unittest/mysqlshdk/libs/azure/signer_t.cc (213 lines of code) (raw):
/*
* Copyright (c) 2022, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "unittest/gprod_clean.h"
#include "mysqlshdk/libs/azure/signer.h"
#include "mysqlshdk/libs/utils/utils_file.h"
#include <set>
#include <string>
#include "mysqlshdk/libs/utils/utils_string.h"
#include "unittest/test_utils/mod_testutils.h"
#include "unittest/mysqlshdk/libs/azure/azure_tests.h"
namespace mysqlshdk {
namespace azure {
class Azure_signer_test : public testing::Test {
public:
static void SetUpTestCase() {
shcore::create_file(
k_config_file,
"[storage]\nconnection_string="
"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey="
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/"
"K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/"
"devstoreaccount1;",
false);
}
static void TearDownTestCase() { shcore::delete_file(k_config_file); }
void SetUp() override {
const auto options = shcore::make_dict(
Blob_storage_options::container_name_option(), "testcontainer",
Blob_storage_options::storage_account_option(), "devstoreaccount1",
Blob_storage_options::config_file_option(), k_config_file);
m_options = std::make_shared<Blob_storage_options>(
mysqlshdk::azure::Blob_storage_options::Operation::WRITE);
Blob_storage_options::options().unpack(options, m_options.get());
m_config = std::make_shared<Blob_storage_config>(*m_options.get(), false);
m_signer = m_config->signer();
m_azure_signer = dynamic_cast<mysqlshdk::azure::Signer *>(m_signer.get());
}
protected:
std::shared_ptr<Blob_storage_options> m_options;
std::shared_ptr<Blob_storage_config> m_config;
std::unique_ptr<rest::Signer> m_signer;
mysqlshdk::azure::Signer *m_azure_signer;
// Friday, 24 May 2013 00:00:00
static constexpr time_t k_now = 1369353600;
static constexpr char k_authorization[] = "authorization";
static constexpr char k_x_ms_date[] = "x-ms-date";
static constexpr char k_x_ms_version[] = "x-ms-version";
static constexpr char k_x_ms_date_value[] = "Fri, 24 May 2013 00:00:00 GMT";
static constexpr char k_x_ms_version_value[] = "2021-08-06";
static constexpr char k_config_file[] = "azure_signer.cfg";
std::string missing_header(const std::string &name) {
return shcore::str_format("Missing '%s header.", name.c_str());
}
std::string mismatched_header(const std::string &name,
const std::string &expected,
const std::string &actual) {
return shcore::str_format(
"Mismatched '%s header.\n-- Expecting: '%s'\n-- Actual: '%s'",
name.c_str(), expected.c_str(), actual.c_str());
}
void test_sign_request(const std::string &context,
const rest::Signed_request *request,
const std::string &signature,
const std::string &expected_signature_prefix,
const std::string &canonical_resource,
const std::string &canonical_headers =
"x-ms-date:Fri, 24 May 2013 00:00:00 "
"GMT\nx-ms-version:2021-08-06\n") {
// Verifies the signature headers
SCOPED_TRACE(context.c_str());
auto rheaders = m_azure_signer->get_required_headers(request, k_now);
EXPECT_EQ(canonical_headers,
m_azure_signer->get_canonical_headers(rheaders));
// Verifies the signature result
EXPECT_EQ(canonical_resource,
m_azure_signer->get_canonical_resource(request));
auto string_to_sign = m_azure_signer->get_string_to_sign(request, rheaders);
auto actual_signature_prefix = string_to_sign.substr(
0, string_to_sign.size() -
(canonical_headers.size() + canonical_resource.size()));
EXPECT_EQ(expected_signature_prefix, actual_signature_prefix);
const auto signer_headers = m_azure_signer->sign_request(request, k_now);
ASSERT_NE(signer_headers.end(), signer_headers.find(k_authorization));
EXPECT_EQ(signature, signer_headers.at(k_authorization));
ASSERT_NE(signer_headers.end(), signer_headers.find(k_x_ms_date));
EXPECT_EQ(k_x_ms_date_value, signer_headers.at(k_x_ms_date));
ASSERT_NE(signer_headers.end(), signer_headers.find(k_x_ms_version));
EXPECT_EQ(k_x_ms_version_value, signer_headers.at(k_x_ms_version));
for (const auto &header : request->headers()) {
ASSERT_NE(signer_headers.end(), signer_headers.find(header.first));
EXPECT_EQ(header.second, signer_headers.at(header.first));
}
}
};
TEST_F(Azure_signer_test, azure_requests) {
Blob_container container(m_config);
auto request = container.list_objects_request("", 0, true, {}, "");
request.type = mysqlshdk::rest::Type::GET;
test_sign_request(
"LIST OBJECTS", &request,
"SharedKey "
"devstoreaccount1:EAGKNX/hpcvYJj5Zyoe8zNu/DtS7ChpE4STrNGkaDXA=",
"GET\n\n\n\n\n\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/"
"testcontainer\ncomp:list\nrestype:container");
request = container.head_object_request("sample.txt");
request.type = mysqlshdk::rest::Type::HEAD;
test_sign_request(
"HEAD OBJECT", &request,
"SharedKey "
"devstoreaccount1:Hdob74EiTY2Kkw19XLF2RTX8EnFpxgeG2xB3vwrKeyU=",
"HEAD\n\n\n\n\n\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/sample.txt");
std::string data = "sample.txt";
request = container.put_object_request(
"sample.txt", {{"x-ms-blob-type", "BlockBlob"},
{"content-type", "application/octet-stream"}});
request.type = mysqlshdk::rest::Type::PUT;
request.body = data.data();
request.size = data.size();
test_sign_request(
"PUT OBJECT", &request,
"SharedKey "
"devstoreaccount1:g4WMGJTsSBzqltLGJTX7/iVF5YL0bqHhbmjDUkLFZy8=",
"PUT\n\n\n10\n\napplication/octet-stream\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/sample.txt",
"x-ms-blob-type:BlockBlob\nx-ms-date:Fri, 24 May 2013 00:00:00 "
"GMT\nx-ms-version:2021-08-06\n");
request = container.delete_object_request("sample.txt");
request.type = mysqlshdk::rest::Type::DELETE;
test_sign_request(
"DELETE OBJECT", &request,
"SharedKey "
"devstoreaccount1:mT3CMI0wxLRWM6w/gXp85tzJKqjkQfsof045ht2OkMs=",
"DELETE\n\n\n\n\napplication/x-www-form-urlencoded\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/sample.txt");
request = container.get_object_request("sample.txt", {});
request.type = mysqlshdk::rest::Type::GET;
test_sign_request(
"GET OBJECT", &request,
"SharedKey "
"devstoreaccount1:kZ3c3DgrUmJHuRDBnAJJPNvXjKR7Tqfkxk286v3E2D8=",
"GET\n\n\n\n\n\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/sample.txt");
request = container.get_object_request("sample.txt", {{"range", "bytes=5-"}});
request.type = mysqlshdk::rest::Type::GET;
test_sign_request(
"GET OBJECT FROM BYTE", &request,
"SharedKey "
"devstoreaccount1:6QOWvxuB39OLiIZ9KqsXSns8Rx7T+KSXmA94JjbmnL4=",
"GET\n\n\n\n\n\n\n\n\n\n\nbytes=5-\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/sample.txt");
request =
container.get_object_request("sample.txt", {{"range", "bytes=5-10"}});
request.type = mysqlshdk::rest::Type::GET;
test_sign_request(
"GET OBJECT RANGE", &request,
"SharedKey "
"devstoreaccount1:S3rtRFTog4bFkOzIz5lN2q9ErnDCOCX8VphDdTVXekE=",
"GET\n\n\n\n\n\n\n\n\n\n\nbytes=5-10\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/sample.txt");
request = container.list_multipart_uploads_request(0);
request.type = mysqlshdk::rest::Type::GET;
test_sign_request(
"LIST MULTIPART UPLOADS", &request,
"SharedKey "
"devstoreaccount1:nKXEe6KsgaLAwUBrWi01Xa17WFQAaLHFUCJLxGRivgs=",
"GET\n\n\n\n\n\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/"
"testcontainer\ncomp:list\ninclude:metadata,uncommittedblobs\nrestype:"
"container");
data = "-";
request = container.create_multipart_upload_request("sample.txt", &data);
request.type = mysqlshdk::rest::Type::PUT;
test_sign_request(
"CREATE MULTIPART UPLOAD", &request,
"SharedKey "
"devstoreaccount1:fNOEmX/yjNv36GVD23riZDiWFLYBAs+tSH6GJ3irj4Y=",
"PUT\n\n\n1\n\napplication/octet-stream\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/"
"sample.txt\nblockid:MDAwMDA=\ncomp:block");
mysqlshdk::storage::backend::object_storage::Multipart_object object;
object.name = "sample.txt";
data = "sample.txt";
request = container.upload_part_request(object, 1, data.size());
request.body = data.data();
request.size = data.size();
request.type = mysqlshdk::rest::Type::PUT;
test_sign_request(
"UPLOAD PART", &request,
"SharedKey "
"devstoreaccount1:yOkmxDsgVv9JrjcFXDVB5ixPla63dWyhIMd55QTi1+0=",
"PUT\n\n\n10\n\napplication/octet-stream\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/"
"sample.txt\nblockid:MDAwMDE=\ncomp:block");
request = container.list_multipart_uploaded_parts_request(object, 0);
request.type = mysqlshdk::rest::Type::GET;
test_sign_request(
"LIST MULTIPART UPLOADED PARTS", &request,
"SharedKey "
"devstoreaccount1:I75Y7dw6PJ4M0i9AI53WSgFlC3eScA1QWP2WksxeYvE=",
"GET\n\n\n\n\n\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/"
"sample.txt\nblocklisttype:uncommitted\ncomp:blocklist");
request = container.commit_multipart_upload_request(object, {}, &data);
request.body = data.c_str();
request.size = data.size();
request.type = mysqlshdk::rest::Type::PUT;
test_sign_request(
"COMMIT MULTIPART UPLOAD", &request,
"SharedKey "
"devstoreaccount1:cPvaqQpFSdLcQ3sfZ3qKL198n6tanURxvOLtx/UZoL8=",
"PUT\n\n\n50\n\ntext/plain\n\n\n\n\n\n\n",
"/devstoreaccount1/devstoreaccount1/testcontainer/"
"sample.txt\ncomp:blocklist");
}
} // namespace azure
} // namespace mysqlshdk