mirror of
https://github.com/alibaba/higress.git
synced 2026-05-03 09:57:26 +08:00
Add plugins (#27)
This commit is contained in:
63
plugins/wasm-cpp/extensions/hmac_auth/BUILD
Normal file
63
plugins/wasm-cpp/extensions/hmac_auth/BUILD
Normal file
@@ -0,0 +1,63 @@
|
||||
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
|
||||
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
|
||||
|
||||
wasm_cc_binary(
|
||||
name = "hmac_auth.wasm",
|
||||
srcs = [
|
||||
"plugin.cc",
|
||||
"plugin.h",
|
||||
"//common:base64.h",
|
||||
],
|
||||
deps = [
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/time",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
"//common:crypto_util",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "hmac_auth_lib",
|
||||
srcs = [
|
||||
"plugin.cc",
|
||||
"//common:base64.h",
|
||||
],
|
||||
hdrs = [
|
||||
"plugin.h",
|
||||
],
|
||||
copts = ["-DNULL_PLUGIN"],
|
||||
deps = [
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/time",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:crypto_util",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "hmac_auth_test",
|
||||
srcs = [
|
||||
"plugin_test.cc",
|
||||
],
|
||||
copts = ["-DNULL_PLUGIN"],
|
||||
deps = [
|
||||
":hmac_auth_lib",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
],
|
||||
linkopts = ["-lcrypt"],
|
||||
)
|
||||
|
||||
declare_wasm_image_targets(
|
||||
name = "hmac_auth",
|
||||
wasm_file = ":hmac_auth.wasm",
|
||||
)
|
||||
509
plugins/wasm-cpp/extensions/hmac_auth/plugin.cc
Normal file
509
plugins/wasm-cpp/extensions/hmac_auth/plugin.cc
Normal file
@@ -0,0 +1,509 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "extensions/hmac_auth/plugin.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <valarray>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_replace.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "common/base64.h"
|
||||
#include "common/crypto_util.h"
|
||||
#include "common/http_util.h"
|
||||
#include "common/json_util.h"
|
||||
|
||||
using ::nlohmann::json;
|
||||
using ::Wasm::Common::JsonArrayIterate;
|
||||
using ::Wasm::Common::JsonGetField;
|
||||
using ::Wasm::Common::JsonObjectIterate;
|
||||
using ::Wasm::Common::JsonValueAs;
|
||||
|
||||
#ifdef NULL_PLUGIN
|
||||
|
||||
namespace proxy_wasm {
|
||||
namespace null_plugin {
|
||||
namespace hmac_auth {
|
||||
|
||||
PROXY_WASM_NULL_PLUGIN_REGISTRY
|
||||
|
||||
#endif
|
||||
|
||||
static RegisterContextFactory register_HmacAuth(
|
||||
CONTEXT_FACTORY(PluginContext), ROOT_FACTORY(PluginRootContext));
|
||||
|
||||
static constexpr std::string_view CA_KEY = "x-ca-key";
|
||||
static constexpr std::string_view CA_SIGNATURE_METHOD = "x-ca-signature-method";
|
||||
static constexpr std::string_view CA_SIGNATURE_HEADERS =
|
||||
"x-ca-signature-headers";
|
||||
static constexpr std::string_view CA_SIGNATURE = "x-ca-signature";
|
||||
static constexpr std::string_view CA_ERRMSG = "x-ca-error-message";
|
||||
static constexpr std::string_view CA_TIMESTAMP = "x-ca-timestamp";
|
||||
|
||||
static constexpr size_t MILLISEC_MIN_LENGTH = 13;
|
||||
|
||||
static constexpr std::array<std::string_view, 5> CHECK_HEADERS{
|
||||
Wasm::Common::Http::Header::Method,
|
||||
Wasm::Common::Http::Header::Accept,
|
||||
Wasm::Common::Http::Header::ContentMD5,
|
||||
Wasm::Common::Http::Header::ContentType,
|
||||
Wasm::Common::Http::Header::Date,
|
||||
};
|
||||
|
||||
static constexpr size_t MAX_BODY_SIZE = 32 * 1024 * 1024;
|
||||
|
||||
static constexpr int64_t NANO_SECONDS = 1000 * 1000 * 1000;
|
||||
|
||||
namespace {
|
||||
|
||||
void deniedInvalidCaKey() {
|
||||
sendLocalResponse(401, "Invalid Key", "Invalid Key", {});
|
||||
}
|
||||
|
||||
void deniedNoSignature() {
|
||||
sendLocalResponse(401, "Empty Signature", "Empty Signature", {});
|
||||
}
|
||||
|
||||
void deniedUnauthorizedConsumer() {
|
||||
sendLocalResponse(403, "Unauthorized Consumer", "Unauthorized Consumer", {});
|
||||
}
|
||||
|
||||
void deniedInvalidCredentials(const std::string& errmsg) {
|
||||
sendLocalResponse(400, "Invalid Signature", "Invalid Signature",
|
||||
{{std::string(CA_ERRMSG), errmsg}});
|
||||
}
|
||||
|
||||
void deniedInvalidContentMD5() {
|
||||
sendLocalResponse(400, "Invalid Content-MD5", "Invalid Content-MD5", {});
|
||||
}
|
||||
|
||||
void deniedInvalidDate() {
|
||||
sendLocalResponse(400, "Invalid Date", "Invalid Date", {});
|
||||
}
|
||||
|
||||
void deniedBodyTooLarge() {
|
||||
sendLocalResponse(413, "Request Body Too Large", "Request Body Too Large",
|
||||
{});
|
||||
}
|
||||
|
||||
std::string getStringToSign() {
|
||||
std::string message;
|
||||
for (const auto& header : CHECK_HEADERS) {
|
||||
auto header_value = getRequestHeader(header)->toString();
|
||||
absl::StrAppendFormat(&message, "%s\n", header_value);
|
||||
}
|
||||
|
||||
auto dynamic_check_headers =
|
||||
getRequestHeader(CA_SIGNATURE_HEADERS)->toString();
|
||||
std::vector<std::string> header_arr;
|
||||
for (const auto& header : absl::StrSplit(dynamic_check_headers, ",")) {
|
||||
auto lower_header = absl::AsciiStrToLower(header);
|
||||
if (lower_header == CA_SIGNATURE || lower_header == CA_SIGNATURE_HEADERS) {
|
||||
continue;
|
||||
}
|
||||
bool is_static = false;
|
||||
for (const auto& h : CHECK_HEADERS) {
|
||||
if (h == lower_header) {
|
||||
is_static = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!is_static) {
|
||||
header_arr.push_back(std::move(lower_header));
|
||||
}
|
||||
}
|
||||
std::sort(header_arr.begin(), header_arr.end());
|
||||
for (const auto& header : header_arr) {
|
||||
auto header_value = getRequestHeader(header)->toString();
|
||||
absl::StrAppendFormat(&message, "%s:%s\n", header, header_value);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
void getStringToSignWithParam(
|
||||
std::string* str_to_sign, const std::string& path,
|
||||
std::optional<std::reference_wrapper<Wasm::Common::Http::QueryParams>>
|
||||
body_params) {
|
||||
// need alphabetical order
|
||||
auto params =
|
||||
Wasm::Common::Http::parseAndDecodeQueryString(std::string(path));
|
||||
if (body_params) {
|
||||
for (auto&& param : body_params.value().get()) {
|
||||
params.emplace(param);
|
||||
}
|
||||
}
|
||||
auto url_path = path.substr(0, path.find('?'));
|
||||
absl::StrAppend(str_to_sign, url_path);
|
||||
if (params.empty()) {
|
||||
return;
|
||||
}
|
||||
str_to_sign->append("?");
|
||||
auto it = params.begin();
|
||||
for (; it != std::prev(params.end()); it++) {
|
||||
absl::StrAppendFormat(str_to_sign, "%s=%s&", it->first, it->second);
|
||||
}
|
||||
absl::StrAppendFormat(str_to_sign, "%s=%s", it->first, it->second);
|
||||
return;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
HmacAuthConfigRule& rule) {
|
||||
if ((configuration.find("consumers") != configuration.end()) &&
|
||||
(configuration.find("credentials") != configuration.end())) {
|
||||
LOG_WARN(
|
||||
"The consumers field and the credentials field cannot appear at the "
|
||||
"same level");
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "credentials", [&](const json& credential) -> bool {
|
||||
auto item = credential.find("key");
|
||||
if (item == credential.end()) {
|
||||
LOG_WARN("can't find 'key' field in credential.");
|
||||
return false;
|
||||
}
|
||||
auto key = JsonValueAs<std::string>(item.value());
|
||||
if (key.second != Wasm::Common::JsonParserResultDetail::OK ||
|
||||
!key.first) {
|
||||
return false;
|
||||
}
|
||||
item = credential.find("secret");
|
||||
if (item == credential.end()) {
|
||||
LOG_WARN("can't find 'secret' field in credential.");
|
||||
return false;
|
||||
}
|
||||
auto secret = JsonValueAs<std::string>(item.value());
|
||||
if (secret.second != Wasm::Common::JsonParserResultDetail::OK ||
|
||||
!secret.first) {
|
||||
return false;
|
||||
}
|
||||
auto result = rule.credentials.emplace(
|
||||
std::make_pair(key.first.value(), secret.first.value()));
|
||||
if (!result.second) {
|
||||
LOG_WARN(absl::StrCat("duplicate credential key: ",
|
||||
key.first.value()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for credentials.");
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "consumers", [&](const json& consumer) -> bool {
|
||||
auto item = consumer.find("key");
|
||||
if (item == consumer.end()) {
|
||||
LOG_WARN("can't find 'key' field in consumer.");
|
||||
return false;
|
||||
}
|
||||
auto key = JsonValueAs<std::string>(item.value());
|
||||
if (key.second != Wasm::Common::JsonParserResultDetail::OK ||
|
||||
!key.first) {
|
||||
return false;
|
||||
}
|
||||
item = consumer.find("secret");
|
||||
if (item == consumer.end()) {
|
||||
LOG_WARN("can't find 'secret' field in consumer.");
|
||||
return false;
|
||||
}
|
||||
auto secret = JsonValueAs<std::string>(item.value());
|
||||
if (secret.second != Wasm::Common::JsonParserResultDetail::OK ||
|
||||
!secret.first) {
|
||||
return false;
|
||||
}
|
||||
item = consumer.find("name");
|
||||
if (item == consumer.end()) {
|
||||
LOG_WARN("can't find 'name' field in consumer.");
|
||||
return false;
|
||||
}
|
||||
auto name = JsonValueAs<std::string>(item.value());
|
||||
if (name.second != Wasm::Common::JsonParserResultDetail::OK ||
|
||||
!name.first) {
|
||||
return false;
|
||||
}
|
||||
if (rule.credentials.find(key.first.value()) !=
|
||||
rule.credentials.end()) {
|
||||
LOG_WARN(
|
||||
absl::StrCat("duplicate consumer key: ", key.first.value()));
|
||||
return false;
|
||||
}
|
||||
rule.credentials.emplace(
|
||||
std::make_pair(key.first.value(), secret.first.value()));
|
||||
rule.key_to_name.emplace(
|
||||
std::make_pair(key.first.value(), name.first.value()));
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for credentials.");
|
||||
return false;
|
||||
}
|
||||
if (rule.credentials.empty()) {
|
||||
LOG_INFO("at least one credential has to be configured for a rule.");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = configuration.find("date_offset");
|
||||
if (it != configuration.end()) {
|
||||
auto date_offset = JsonValueAs<int64_t>(it.value());
|
||||
if (date_offset.second != Wasm::Common::JsonParserResultDetail::OK ||
|
||||
!date_offset.first) {
|
||||
LOG_WARN("failed to parse 'date_offset' field in configuration.");
|
||||
return false;
|
||||
}
|
||||
rule.date_nano_offset = date_offset.first.value() * NANO_SECONDS;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginRootContext::checkConsumer(
|
||||
const std::string& ca_key, const HmacAuthConfigRule& rule,
|
||||
const std::optional<std::unordered_set<std::string>>& allow_set) {
|
||||
if (ca_key.empty()) {
|
||||
LOG_DEBUG("empty key");
|
||||
deniedInvalidCaKey();
|
||||
return false;
|
||||
}
|
||||
auto credentials_iter = rule.credentials.find(std::string(ca_key));
|
||||
if (credentials_iter == rule.credentials.end()) {
|
||||
LOG_DEBUG(absl::StrCat("can't find secret through key: ", ca_key));
|
||||
deniedInvalidCaKey();
|
||||
return false;
|
||||
}
|
||||
auto key_to_name_iter = rule.key_to_name.find(std::string(ca_key));
|
||||
if (key_to_name_iter != rule.key_to_name.end()) {
|
||||
if (allow_set && !allow_set.value().empty()) {
|
||||
if (allow_set.value().find(key_to_name_iter->second) ==
|
||||
allow_set.value().end()) {
|
||||
LOG_DEBUG(absl::StrCat("consumer is not allowed: ",
|
||||
key_to_name_iter->second));
|
||||
deniedUnauthorizedConsumer();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
addRequestHeader("X-Mse-Consumer", key_to_name_iter->second);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginRootContext::checkPlugin(
|
||||
const std::string& ca_key, const std::string& signature,
|
||||
const std::string& signature_method, const std::string& path,
|
||||
const std::string& date, bool is_timetamp, std::string* sts,
|
||||
const HmacAuthConfigRule& rule,
|
||||
std::optional<std::reference_wrapper<Wasm::Common::Http::QueryParams>>
|
||||
body_params) {
|
||||
if (ca_key.empty()) {
|
||||
LOG_DEBUG("empty key");
|
||||
deniedInvalidCaKey();
|
||||
return false;
|
||||
}
|
||||
if (signature.empty()) {
|
||||
LOG_DEBUG("empty signature");
|
||||
deniedNoSignature();
|
||||
return false;
|
||||
}
|
||||
int64_t time_offset = 0;
|
||||
if (rule.date_nano_offset > 0) {
|
||||
auto current_time = getCurrentTimeNanoseconds();
|
||||
if (!is_timetamp) {
|
||||
auto time_from_date = Wasm::Common::Http::httpTime(date);
|
||||
if (!Wasm::Common::Http::timePointValid(time_from_date)) {
|
||||
LOG_DEBUG(absl::StrFormat("invalid date format: %s", date));
|
||||
deniedInvalidDate();
|
||||
return false;
|
||||
}
|
||||
time_offset = std::abs(
|
||||
(long long)(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
time_from_date.time_since_epoch())
|
||||
.count() -
|
||||
current_time));
|
||||
} else {
|
||||
int64_t timestamp;
|
||||
if (!absl::SimpleAtoi(date, ×tamp)) {
|
||||
LOG_DEBUG(absl::StrFormat("invalid timestamp format: %s", date));
|
||||
deniedInvalidDate();
|
||||
return false;
|
||||
}
|
||||
time_offset = std::abs((long long)(timestamp - current_time));
|
||||
// milliseconds to nanoseconds
|
||||
time_offset *= 1e6;
|
||||
// seconds
|
||||
if (date.size() < MILLISEC_MIN_LENGTH) {
|
||||
time_offset *= 1e3;
|
||||
}
|
||||
}
|
||||
if (time_offset > rule.date_nano_offset) {
|
||||
LOG_DEBUG(absl::StrFormat("date expired, offset is: %u",
|
||||
time_offset / NANO_SECONDS));
|
||||
deniedInvalidDate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::string hash_type{"sha256"};
|
||||
if (signature_method == "HmacSHA1") {
|
||||
hash_type = "sha1";
|
||||
}
|
||||
auto credentials_iter = rule.credentials.find(std::string(ca_key));
|
||||
if (credentials_iter == rule.credentials.end()) {
|
||||
LOG_DEBUG(absl::StrCat("can't find secret through key: ", ca_key));
|
||||
deniedInvalidCaKey();
|
||||
return false;
|
||||
}
|
||||
const auto& secret = credentials_iter->second;
|
||||
getStringToSignWithParam(sts, path, body_params);
|
||||
const auto& str_to_sign = *sts;
|
||||
auto hmac =
|
||||
Wasm::Common::Crypto::getShaHmacBase64(hash_type, secret, str_to_sign);
|
||||
if (hmac != signature) {
|
||||
auto tip = absl::StrReplaceAll(str_to_sign, {{"\n", "#"}});
|
||||
LOG_DEBUG(absl::StrCat("invalid signature, stringToSign: ", tip,
|
||||
" signature: ", hmac));
|
||||
deniedInvalidCredentials(absl::StrFormat("Server StringToSign:`%s`", tip));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginRootContext::onConfigure(size_t size) {
|
||||
// Parse configuration JSON string.
|
||||
if (size > 0 && !configure(size)) {
|
||||
LOG_WARN("configuration has errors initialization will not continue.");
|
||||
setInvalidConfig();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginRootContext::configure(size_t configuration_size) {
|
||||
auto configuration_data = getBufferBytes(WasmBufferType::PluginConfiguration,
|
||||
0, configuration_size);
|
||||
// Parse configuration JSON string.
|
||||
auto result = ::Wasm::Common::JsonParse(configuration_data->view());
|
||||
if (!result) {
|
||||
LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ",
|
||||
configuration_data->view()));
|
||||
return false;
|
||||
}
|
||||
if (!parseAuthRuleConfig(result.value())) {
|
||||
LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ",
|
||||
configuration_data->view()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) {
|
||||
ca_key_ = getRequestHeader(CA_KEY)->toString();
|
||||
signature_ = getRequestHeader(CA_SIGNATURE)->toString();
|
||||
signature_method_ = getRequestHeader(CA_SIGNATURE_METHOD)->toString();
|
||||
path_ = getRequestHeader(Wasm::Common::Http::Header::Path)->toString();
|
||||
date_ = getRequestHeader(Wasm::Common::Http::Header::Date)->toString();
|
||||
str_to_sign_ = getStringToSign();
|
||||
body_md5_ =
|
||||
getRequestHeader(Wasm::Common::Http::Header::ContentMD5)->toString();
|
||||
GET_HEADER_VIEW(Wasm::Common::Http::Header::ContentType, content_type);
|
||||
|
||||
if (date_.empty()) {
|
||||
date_ = getRequestHeader(CA_TIMESTAMP)->toString();
|
||||
is_timestamp_ = true;
|
||||
}
|
||||
auto* rootCtx = rootContext();
|
||||
|
||||
auto config = rootCtx->getMatchAuthConfig();
|
||||
config_ = config.first;
|
||||
if (!config_) {
|
||||
return FilterHeadersStatus::Continue;
|
||||
}
|
||||
allow_set_ = config.second;
|
||||
// check if ca_key present in config and it's consumer_name is allowed
|
||||
if (!rootCtx->checkConsumer(ca_key_, config_.value(), allow_set_)) {
|
||||
return FilterHeadersStatus::StopIteration;
|
||||
}
|
||||
|
||||
if (absl::StrContains(absl::AsciiStrToLower(content_type),
|
||||
"application/x-www-form-urlencoded")) {
|
||||
check_body_params_ = true;
|
||||
return FilterHeadersStatus::Continue;
|
||||
}
|
||||
|
||||
return rootCtx->checkPlugin(ca_key_, signature_, signature_method_, path_,
|
||||
date_, is_timestamp_, &str_to_sign_,
|
||||
config_.value(), std::nullopt)
|
||||
? FilterHeadersStatus::Continue
|
||||
: FilterHeadersStatus::StopIteration;
|
||||
}
|
||||
|
||||
FilterDataStatus PluginContext::onRequestBody(size_t body_size,
|
||||
bool end_stream) {
|
||||
if (!config_) {
|
||||
return FilterDataStatus::Continue;
|
||||
}
|
||||
if (body_md5_.empty() && !check_body_params_) {
|
||||
return FilterDataStatus::Continue;
|
||||
}
|
||||
body_total_size_ += body_size;
|
||||
if (body_total_size_ > MAX_BODY_SIZE) {
|
||||
LOG_DEBUG("body_size is too large");
|
||||
deniedBodyTooLarge();
|
||||
return FilterDataStatus::StopIterationNoBuffer;
|
||||
}
|
||||
if (!end_stream) {
|
||||
return FilterDataStatus::StopIterationAndBuffer;
|
||||
}
|
||||
auto body =
|
||||
getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_total_size_);
|
||||
LOG_DEBUG("body: " + body->toString());
|
||||
if (!body_md5_.empty()) {
|
||||
if (body->size() == 0) {
|
||||
LOG_DEBUG("got empty body");
|
||||
deniedInvalidContentMD5();
|
||||
return FilterDataStatus::StopIterationNoBuffer;
|
||||
}
|
||||
auto md5 = Wasm::Common::Crypto::getMD5Base64(body->view());
|
||||
if (md5 != body_md5_) {
|
||||
LOG_DEBUG(
|
||||
absl::StrFormat("body md5 expect: %s, actual: %s", body_md5_, md5));
|
||||
deniedInvalidContentMD5();
|
||||
return FilterDataStatus::StopIterationNoBuffer;
|
||||
}
|
||||
}
|
||||
if (check_body_params_) {
|
||||
auto body_params = Wasm::Common::Http::parseFromBody(body->view());
|
||||
auto* rootCtx = rootContext();
|
||||
return rootCtx->checkPlugin(ca_key_, signature_, signature_method_, path_,
|
||||
date_, is_timestamp_, &str_to_sign_,
|
||||
config_.value(), body_params)
|
||||
? FilterDataStatus::Continue
|
||||
: FilterDataStatus::StopIterationNoBuffer;
|
||||
}
|
||||
return FilterDataStatus::Continue;
|
||||
}
|
||||
|
||||
#ifdef NULL_PLUGIN
|
||||
|
||||
} // namespace hmac_auth
|
||||
} // namespace null_plugin
|
||||
} // namespace proxy_wasm
|
||||
|
||||
#endif
|
||||
105
plugins/wasm-cpp/extensions/hmac_auth/plugin.h
Normal file
105
plugins/wasm-cpp/extensions/hmac_auth/plugin.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/http_util.h"
|
||||
#include "common/route_rule_matcher.h"
|
||||
#define ASSERT(_X) assert(_X)
|
||||
|
||||
#ifndef NULL_PLUGIN
|
||||
|
||||
#include "proxy_wasm_intrinsics.h"
|
||||
|
||||
#else
|
||||
|
||||
#include "include/proxy-wasm/null_plugin.h"
|
||||
|
||||
namespace proxy_wasm {
|
||||
namespace null_plugin {
|
||||
namespace hmac_auth {
|
||||
|
||||
#endif
|
||||
|
||||
struct HmacAuthConfigRule {
|
||||
std::unordered_map<std::string, std::string> credentials;
|
||||
std::unordered_map<std::string, std::string> key_to_name;
|
||||
int64_t date_nano_offset = -1;
|
||||
};
|
||||
|
||||
// PluginRootContext is the root context for all streams processed by the
|
||||
// thread. It has the same lifetime as the worker thread and acts as target for
|
||||
// interactions that outlives individual stream, e.g. timer, async calls.
|
||||
class PluginRootContext : public RootContext,
|
||||
public RouteRuleMatcher<HmacAuthConfigRule> {
|
||||
public:
|
||||
PluginRootContext(uint32_t id, std::string_view root_id)
|
||||
: RootContext(id, root_id) {}
|
||||
~PluginRootContext() {}
|
||||
bool onConfigure(size_t) override;
|
||||
bool checkPlugin(
|
||||
const std::string& ca_key, const std::string& signature,
|
||||
const std::string& signature_method, const std::string& path,
|
||||
const std::string& date, bool is_timestamp, std::string* sts,
|
||||
const HmacAuthConfigRule&,
|
||||
std::optional<std::reference_wrapper<Wasm::Common::Http::QueryParams>>);
|
||||
bool checkConsumer(const std::string&, const HmacAuthConfigRule&,
|
||||
const std::optional<std::unordered_set<std::string>>&);
|
||||
bool configure(size_t);
|
||||
|
||||
private:
|
||||
bool parsePluginConfig(const json&, HmacAuthConfigRule&) override;
|
||||
};
|
||||
|
||||
// Per-stream context.
|
||||
class PluginContext : public Context {
|
||||
public:
|
||||
explicit PluginContext(uint32_t id, RootContext* root) : Context(id, root) {}
|
||||
FilterHeadersStatus onRequestHeaders(uint32_t, bool) override;
|
||||
FilterDataStatus onRequestBody(size_t, bool) override;
|
||||
|
||||
private:
|
||||
inline PluginRootContext* rootContext() {
|
||||
return dynamic_cast<PluginRootContext*>(this->root());
|
||||
}
|
||||
|
||||
std::string ca_key_;
|
||||
std::string signature_;
|
||||
std::string signature_method_;
|
||||
std::string path_;
|
||||
std::string date_;
|
||||
std::string str_to_sign_;
|
||||
std::string body_md5_;
|
||||
bool is_timestamp_ = false;
|
||||
std::optional<std::reference_wrapper<HmacAuthConfigRule>> config_;
|
||||
std::optional<std::unordered_set<std::string>> allow_set_;
|
||||
bool check_body_params_ = false;
|
||||
size_t body_total_size_ = 0;
|
||||
};
|
||||
|
||||
#ifdef NULL_PLUGIN
|
||||
|
||||
} // namespace hmac_auth
|
||||
} // namespace null_plugin
|
||||
} // namespace proxy_wasm
|
||||
|
||||
#endif
|
||||
599
plugins/wasm-cpp/extensions/hmac_auth/plugin_test.cc
Normal file
599
plugins/wasm-cpp/extensions/hmac_auth/plugin_test.cc
Normal file
@@ -0,0 +1,599 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "extensions/hmac_auth/plugin.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "common/base64.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "include/proxy-wasm/context.h"
|
||||
#include "include/proxy-wasm/null.h"
|
||||
|
||||
namespace proxy_wasm {
|
||||
namespace null_plugin {
|
||||
namespace hmac_auth {
|
||||
|
||||
NullPluginRegistry* context_registry_;
|
||||
RegisterNullVmPluginFactory register_hmac_auth_plugin("hmac_auth", []() {
|
||||
return std::make_unique<NullPlugin>(hmac_auth::context_registry_);
|
||||
});
|
||||
|
||||
class MockContext : public proxy_wasm::ContextBase {
|
||||
public:
|
||||
MockContext(WasmBase* wasm) : ContextBase(wasm) {}
|
||||
|
||||
MOCK_METHOD(BufferInterface*, getBuffer, (WasmBufferType));
|
||||
MOCK_METHOD(WasmResult, log, (uint32_t, std::string_view));
|
||||
MOCK_METHOD(WasmResult, getHeaderMapValue,
|
||||
(WasmHeaderMapType /* type */, std::string_view /* key */,
|
||||
std::string_view* /*result */));
|
||||
MOCK_METHOD(WasmResult, addHeaderMapValue,
|
||||
(WasmHeaderMapType /* type */, std::string_view /* key */,
|
||||
std::string_view /* value */));
|
||||
MOCK_METHOD(WasmResult, sendLocalResponse,
|
||||
(uint32_t /* response_code */, std::string_view /* body */,
|
||||
Pairs /* additional_headers */, uint32_t /* grpc_status */,
|
||||
std::string_view /* details */));
|
||||
MOCK_METHOD(uint64_t, getCurrentTimeNanoseconds, ());
|
||||
MOCK_METHOD(WasmResult, getProperty, (std::string_view, std::string*));
|
||||
};
|
||||
|
||||
class HmacAuthTest : public ::testing::Test {
|
||||
protected:
|
||||
HmacAuthTest() {
|
||||
// Initialize test VM
|
||||
test_vm_ = createNullVm();
|
||||
wasm_base_ = std::make_unique<WasmBase>(
|
||||
std::move(test_vm_), "test-vm", "", "",
|
||||
std::unordered_map<std::string, std::string>{},
|
||||
AllowedCapabilitiesMap{});
|
||||
wasm_base_->load("hmac_auth");
|
||||
wasm_base_->initialize();
|
||||
|
||||
// Initialize host side context
|
||||
mock_context_ = std::make_unique<MockContext>(wasm_base_.get());
|
||||
current_context_ = mock_context_.get();
|
||||
|
||||
ON_CALL(*mock_context_, log(testing::_, testing::_))
|
||||
.WillByDefault([](uint32_t, std::string_view m) {
|
||||
std::cerr << m << "\n";
|
||||
return WasmResult::Ok;
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context_, getHeaderMapValue(WasmHeaderMapType::RequestHeaders,
|
||||
testing::_, testing::_))
|
||||
.WillByDefault([&](WasmHeaderMapType, std::string_view header,
|
||||
std::string_view* result) {
|
||||
auto it = headers_.find(std::string(header));
|
||||
if (it == headers_.end()) {
|
||||
std::cerr << header << " not found.\n";
|
||||
return WasmResult::NotFound;
|
||||
}
|
||||
*result = it->second;
|
||||
return WasmResult::Ok;
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context_, addHeaderMapValue(WasmHeaderMapType::RequestHeaders,
|
||||
testing::_, testing::_))
|
||||
.WillByDefault([&](WasmHeaderMapType, std::string_view key,
|
||||
std::string_view value) { return WasmResult::Ok; });
|
||||
|
||||
ON_CALL(*mock_context_, getBuffer(testing::_))
|
||||
.WillByDefault([&](WasmBufferType type) {
|
||||
if (type == WasmBufferType::HttpRequestBody) {
|
||||
return &body_;
|
||||
}
|
||||
return &config_;
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context_, getCurrentTimeNanoseconds()).WillByDefault([&]() {
|
||||
return current_time_;
|
||||
});
|
||||
|
||||
ON_CALL(*mock_context_, getProperty(testing::_, testing::_))
|
||||
.WillByDefault([&](std::string_view path, std::string* result) {
|
||||
*result = route_name_;
|
||||
return WasmResult::Ok;
|
||||
});
|
||||
|
||||
// Initialize Wasm sandbox context
|
||||
root_context_ = std::make_unique<PluginRootContext>(0, "");
|
||||
context_ = std::make_unique<PluginContext>(1, root_context_.get());
|
||||
}
|
||||
~HmacAuthTest() override {}
|
||||
|
||||
std::unique_ptr<WasmBase> wasm_base_;
|
||||
std::unique_ptr<WasmVm> test_vm_;
|
||||
std::unique_ptr<MockContext> mock_context_;
|
||||
|
||||
std::unique_ptr<PluginRootContext> root_context_;
|
||||
std::unique_ptr<PluginContext> context_;
|
||||
|
||||
std::map<std::string, std::string> headers_;
|
||||
std::string route_name_;
|
||||
BufferBase body_;
|
||||
BufferBase config_;
|
||||
uint64_t current_time_;
|
||||
};
|
||||
|
||||
TEST_F(HmacAuthTest, Sign) {
|
||||
headers_ = {
|
||||
{":path",
|
||||
"/http2test/test?param1=test&username=xiaoming&password=123456789"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json; charset=utf-8"},
|
||||
{"ca_version", "1"},
|
||||
{"content-type", "application/x-www-form-urlencoded; charset=utf-8"},
|
||||
{"x-ca-timestamp", "1525872629832"},
|
||||
{"date", "Wed, 09 May 2018 13:30:29 GMT+00:00"},
|
||||
{"user-agent", "ALIYUN-ANDROID-DEMO"},
|
||||
{"x-ca-nonce", "c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44"},
|
||||
{"content-length", "33"},
|
||||
{"username", "xiaoming&password=123456789"},
|
||||
{"x-ca-key", "203753385"},
|
||||
{"x-ca-signature-method", "HmacSHA256"},
|
||||
{"x-ca-signature", "xfX+bZxY2yl7EB/qdoDy9v/uscw3Nnj1pgoU+Bm6xdM="},
|
||||
{"x-ca-signature-headers",
|
||||
"x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method"},
|
||||
};
|
||||
// auto actual = root_context_->getStringToSign(
|
||||
// "/http2test/test?param1=test&username=xiaoming&password=123456789",
|
||||
// std::nullopt);
|
||||
// EXPECT_EQ(actual, R"(POST
|
||||
// application/json; charset=utf-8
|
||||
|
||||
// application/x-www-form-urlencoded; charset=utf-8
|
||||
// Wed, 09 May 2018 13:30:29 GMT+00:00
|
||||
// x-ca-key:203753385
|
||||
// x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
|
||||
// x-ca-signature-method:HmacSHA256
|
||||
// x-ca-timestamp:1525872629832
|
||||
// /http2test/test?param1=test&password=123456789&username=xiaoming)");
|
||||
|
||||
headers_ = {
|
||||
{":path", "/Third/Tools/checkSign"},
|
||||
{":method", "GET"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/json"},
|
||||
{"x-ca-timestamp", "1646365291734"},
|
||||
{"x-ca-nonce", "787dd0c2-7bd8-41cd-9c19-62c05ea524a2"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "EdJSFAMOWyXZOpXhevZnjuS0ZafnwnCqaSk5hz+tXo8="},
|
||||
};
|
||||
HmacAuthConfigRule rule;
|
||||
rule.credentials = {{"appKey", "appSecret"}};
|
||||
// EXPECT_EQ(root_context_->checkPlugin(rule, std::nullopt), true);
|
||||
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"_rules_": [
|
||||
{
|
||||
"_match_route_":["test"],
|
||||
"credentials":[
|
||||
{"key": "appKey", "secret": "appSecret"}
|
||||
]
|
||||
}
|
||||
]
|
||||
})";
|
||||
route_name_ = "test";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, SignWithConsumer) {
|
||||
headers_ = {
|
||||
{":path", "/Third/Tools/checkSign"},
|
||||
{":method", "GET"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/json"},
|
||||
{"x-ca-timestamp", "1646365291734"},
|
||||
{"x-ca-nonce", "787dd0c2-7bd8-41cd-9c19-62c05ea524a2"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "EdJSFAMOWyXZOpXhevZnjuS0ZafnwnCqaSk5hz+tXo8="},
|
||||
};
|
||||
HmacAuthConfigRule rule;
|
||||
rule.credentials = {{"appKey", "appSecret"}};
|
||||
// EXPECT_EQ(root_context_->checkPlugin(rule, std::nullopt), true);
|
||||
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [{"key": "appKey", "secret": "appSecret", "name": "consumer"}],
|
||||
"_rules_": [
|
||||
{
|
||||
"_match_route_":["test"],
|
||||
"allow":["consumer"]
|
||||
}
|
||||
]
|
||||
})";
|
||||
route_name_ = "test";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, ParamInBody) {
|
||||
headers_ = {
|
||||
{":path", "/http2test/test?param1=test"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json; charset=utf-8"},
|
||||
{"ca_version", "1"},
|
||||
{"content-type", "application/x-www-form-urlencoded; charset=utf-8"},
|
||||
{"x-ca-timestamp", "1525872629832"},
|
||||
{"date", "Wed, 09 May 2018 13:30:29 GMT+00:00"},
|
||||
{"user-agent", "ALIYUN-ANDROID-DEMO"},
|
||||
{"x-ca-nonce", "c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44"},
|
||||
{"content-length", "33"},
|
||||
{"username", "xiaoming&password=123456789"},
|
||||
{"x-ca-key", "203753385"},
|
||||
{"x-ca-signature-method", "HmacSHA256"},
|
||||
{"x-ca-signature", "xfX+bZxY2yl7EB/qdoDy9v/uscw3Nnj1pgoU+Bm6xdM="},
|
||||
{"x-ca-signature-headers",
|
||||
"x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method"},
|
||||
};
|
||||
Wasm::Common::Http::QueryParams body_params = {{"username", "xiaoming"},
|
||||
{"password", "123456789"}};
|
||||
// auto actual =
|
||||
// root_context_->getStringToSign("/http2test/test?param1=test",
|
||||
// body_params);
|
||||
// EXPECT_EQ(actual, R"(POST
|
||||
// application/json; charset=utf-8
|
||||
|
||||
// application/x-www-form-urlencoded; charset=utf-8
|
||||
// Wed, 09 May 2018 13:30:29 GMT+00:00
|
||||
// x-ca-key:203753385
|
||||
// x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
|
||||
// x-ca-signature-method:HmacSHA256
|
||||
// x-ca-timestamp:1525872629832
|
||||
// /http2test/test?param1=test&password=123456789&username=xiaoming)");
|
||||
|
||||
headers_ = {
|
||||
{":path", "/Third/User/getNyAccessToken"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/x-www-form-urlencoded"},
|
||||
{"x-ca-timestamp", "1646646682418"},
|
||||
{"x-ca-nonce", "ca5a6753-b76c-4fff-a9d9-e5bb643e8cdf"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "gmf9xq0hc95Hmt+7G+OocS009ka3v1v0rvfshKzYc3w="},
|
||||
};
|
||||
HmacAuthConfigRule rule;
|
||||
rule.credentials = {{"appKey", "appSecret"}};
|
||||
body_params = {{"nickname", "nickname"},
|
||||
{"room_id", "6893"},
|
||||
{"uuid", "uuid"},
|
||||
{"photo", "photo"}};
|
||||
// EXPECT_EQ(root_context_->checkPlugin(rule, body_params), true);
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"_rules_": [
|
||||
{
|
||||
"_match_route_":["test"],
|
||||
"credentials":[
|
||||
{"key": "appKey", "secret": "appSecret"}
|
||||
]
|
||||
}
|
||||
]
|
||||
})";
|
||||
route_name_ = "test";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
std::string body("nickname=nickname&room_id=6893&uuid=uuid&photo=photo");
|
||||
body_.set(body);
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_EQ(context_->onRequestBody(body.size(), true),
|
||||
FilterDataStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, ParamInBodyWithConsumer) {
|
||||
headers_ = {
|
||||
{":path", "/Third/User/getNyAccessToken"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/x-www-form-urlencoded"},
|
||||
{"x-ca-timestamp", "1646646682418"},
|
||||
{"x-ca-nonce", "ca5a6753-b76c-4fff-a9d9-e5bb643e8cdf"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "gmf9xq0hc95Hmt+7G+OocS009ka3v1v0rvfshKzYc3w="},
|
||||
};
|
||||
HmacAuthConfigRule rule;
|
||||
rule.credentials = {{"appKey", "appSecret"}};
|
||||
Wasm::Common::Http::QueryParams body_params = {{"nickname", "nickname"},
|
||||
{"room_id", "6893"},
|
||||
{"uuid", "uuid"},
|
||||
{"photo", "photo"}};
|
||||
// EXPECT_EQ(root_context_->checkPlugin(rule, body_params), true);
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [{"key": "appKey", "secret": "appSecret", "name": "consumer"}],
|
||||
"_rules_": [
|
||||
{
|
||||
"_match_route_":["test"],
|
||||
"allow":["consumer"]
|
||||
}
|
||||
]
|
||||
})";
|
||||
route_name_ = "test";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
std::string body("nickname=nickname&room_id=6893&uuid=uuid&photo=photo");
|
||||
body_.set(body);
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_EQ(context_->onRequestBody(body.size(), true),
|
||||
FilterDataStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, ParamInBodyWrongSignature) {
|
||||
headers_ = {
|
||||
{":path", "/Third/User/getNyAccessToken"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/x-www-form-urlencoded"},
|
||||
{"x-ca-timestamp", "1646646682418"},
|
||||
{"x-ca-nonce", "ca5a6753-b76c-4fff-a9d9-e5bb643e8cdf"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "wrong"},
|
||||
};
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"_rules_": [
|
||||
{
|
||||
"_match_route_":["test"],
|
||||
"credentials":[
|
||||
{"key": "appKey", "secret": "appSecret"}
|
||||
]
|
||||
}
|
||||
]
|
||||
})";
|
||||
route_name_ = "test";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
std::string body("nickname=nickname&room_id=6893&uuid=uuid&photo=photo");
|
||||
body_.set(body);
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(400, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestBody(body.size(), true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, InvalidSecret) {
|
||||
{
|
||||
headers_ = {
|
||||
{":path", "/Third/Tools/checkSign"},
|
||||
{":method", "GET"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/json"},
|
||||
{"x-ca-timestamp", "1646365291734"},
|
||||
{"x-ca-nonce", "787dd0c2-7bd8-41cd-9c19-62c05ea524a2"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "EdJSFAMOWyXZOpXhevZnjuS0ZafnwnCqaSk5hz+tXo8="},
|
||||
};
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"credentials":[
|
||||
{"key": "appKey", "secret": ""}
|
||||
]
|
||||
})";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
}
|
||||
|
||||
{
|
||||
headers_ = {
|
||||
{":path", "/Third/Tools/checkSign"},
|
||||
{":method", "GET"},
|
||||
{"accept", "application/json"},
|
||||
{"content-type", "application/json"},
|
||||
{"x-ca-timestamp", "1646365291734"},
|
||||
{"x-ca-nonce", "787dd0c2-7bd8-41cd-9c19-62c05ea524a2"},
|
||||
{"x-ca-key", "appKey"},
|
||||
{"x-ca-signature-headers", "x-ca-key,x-ca-nonce,x-ca-timestamp"},
|
||||
{"x-ca-signature", "EdJSFAMOWyXZOpXhevZnjuS0ZafnwnCqaSk5hz+tXo8="},
|
||||
};
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers":[
|
||||
{"key": "appKey", "secret": "", "name": "consumer1"}
|
||||
]
|
||||
})";
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, DuplicateKey) {
|
||||
{
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"credentials":[
|
||||
{"key": "appKey", "secret": ""},
|
||||
{"key": "appKey", "secret": "123"}
|
||||
]
|
||||
})";
|
||||
BufferBase buffer;
|
||||
config_.set(configuration);
|
||||
EXPECT_FALSE(root_context_->configure(configuration.size()));
|
||||
}
|
||||
|
||||
{
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers":[
|
||||
{"key": "appKey", "secret": "", "name": "consumer1"},
|
||||
{"key": "appKey", "secret": "123", "name": "consumer2"}
|
||||
]
|
||||
})";
|
||||
BufferBase buffer;
|
||||
config_.set(configuration);
|
||||
EXPECT_FALSE(root_context_->configure(configuration.size()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, BodyMD5) {
|
||||
body_.set("abc");
|
||||
headers_ = {{"content-md5", "kAFQmDzST7DWlj99KOF/cg=="}};
|
||||
context_->onRequestHeaders(0, false);
|
||||
EXPECT_EQ(context_->onRequestBody(3, true), FilterDataStatus::Continue);
|
||||
|
||||
headers_ = {};
|
||||
context_->onRequestHeaders(0, false);
|
||||
EXPECT_EQ(context_->onRequestBody(0, false), FilterDataStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, DateCheck) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"credentials":[
|
||||
{"key": "203753385", "secret": "123456"}
|
||||
],
|
||||
"date_offset": 3600
|
||||
})";
|
||||
BufferBase buffer;
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
headers_ = {
|
||||
{":path",
|
||||
"/http2test/test?param1=test&username=xiaoming&password=123456789"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json; charset=utf-8"},
|
||||
{"ca_version", "1"},
|
||||
{"content-type", "application/x-www-form-urlencoded; charset=utf-8"},
|
||||
{"x-ca-timestamp", "1525872629832"},
|
||||
{"date", "Wed, 09 May 2018 13:30:29 GMT+00:00"},
|
||||
{"user-agent", "ALIYUN-ANDROID-DEMO"},
|
||||
{"x-ca-nonce", "c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44"},
|
||||
{"content-length", "33"},
|
||||
{"username", "xiaoming&password=123456789"},
|
||||
{"x-ca-key", "203753385"},
|
||||
{"x-ca-signature-method", "HmacSHA256"},
|
||||
{"x-ca-signature", "FJbhmAFYz9zfl1FrThxzxBt79BvaHQIzy8Wpctn+xXE="},
|
||||
{"x-ca-signature-headers",
|
||||
"x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method"},
|
||||
};
|
||||
current_time_ = (uint64_t)1525876230 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_EQ(context_->onRequestBody(0, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
current_time_ = (uint64_t)1525869027 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestBody(0, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
current_time_ = (uint64_t)1525869029 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestBody(0, true), FilterDataStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, TimestampCheck) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"credentials":[
|
||||
{"key": "203753385", "secret": "123456"}
|
||||
],
|
||||
"date_offset": 3600
|
||||
})";
|
||||
BufferBase buffer;
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
headers_ = {
|
||||
{":path",
|
||||
"/http2test/test?param1=test&username=xiaoming&password=123456789"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json; charset=utf-8"},
|
||||
{"ca_version", "1"},
|
||||
{"content-type", "application/x-www-form-urlencoded; charset=utf-8"},
|
||||
{"x-ca-timestamp", "1525872629832"},
|
||||
{"user-agent", "ALIYUN-ANDROID-DEMO"},
|
||||
{"x-ca-nonce", "c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44"},
|
||||
{"content-length", "33"},
|
||||
{"username", "xiaoming&password=123456789"},
|
||||
{"x-ca-key", "203753385"},
|
||||
{"x-ca-signature-method", "HmacSHA256"},
|
||||
{"x-ca-signature", "wcQC8014+HW0TumVfXy8+UXI4JDvkhjPlqp6rTE7cZo="},
|
||||
{"x-ca-signature-headers",
|
||||
"x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method"},
|
||||
};
|
||||
current_time_ = (uint64_t)1525876230 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_EQ(context_->onRequestBody(0, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
current_time_ = (uint64_t)1525869027 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestBody(0, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
current_time_ = (uint64_t)1525869029 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestBody(0, true), FilterDataStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(HmacAuthTest, TimestampSecCheck) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"credentials":[
|
||||
{"key": "203753385", "secret": "123456"}
|
||||
],
|
||||
"date_offset": 3600
|
||||
})";
|
||||
BufferBase buffer;
|
||||
config_.set(configuration);
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
headers_ = {
|
||||
{":path",
|
||||
"/http2test/test?param1=test&username=xiaoming&password=123456789"},
|
||||
{":method", "POST"},
|
||||
{"accept", "application/json; charset=utf-8"},
|
||||
{"ca_version", "1"},
|
||||
{"content-type", "application/x-www-form-urlencoded; charset=utf-8"},
|
||||
{"x-ca-timestamp", "1525872629"},
|
||||
{"user-agent", "ALIYUN-ANDROID-DEMO"},
|
||||
{"x-ca-nonce", "c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44"},
|
||||
{"content-length", "33"},
|
||||
{"username", "xiaoming&password=123456789"},
|
||||
{"x-ca-key", "203753385"},
|
||||
{"x-ca-signature-method", "HmacSHA256"},
|
||||
{"x-ca-signature", "7yl5Rba+3pnp9weLP3af1Hejz4K3RFp+BHL7N2w98/U="},
|
||||
{"x-ca-signature-headers",
|
||||
"x-ca-timestamp,x-ca-key,x-ca-nonce,x-ca-signature-method"},
|
||||
};
|
||||
current_time_ = (uint64_t)1525876230 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_EQ(context_->onRequestBody(0, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
current_time_ = (uint64_t)1525869027 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestBody(0, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
current_time_ = (uint64_t)1525869029 * 1000 * 1000 * 1000;
|
||||
EXPECT_EQ(context_->onRequestBody(0, true), FilterDataStatus::Continue);
|
||||
}
|
||||
|
||||
} // namespace hmac_auth
|
||||
} // namespace null_plugin
|
||||
} // namespace proxy_wasm
|
||||
Reference in New Issue
Block a user