summaryrefslogtreecommitdiffstats
path: root/src/test/rgw/test_rgw_kms.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/rgw/test_rgw_kms.cc')
-rw-r--r--src/test/rgw/test_rgw_kms.cc282
1 files changed, 282 insertions, 0 deletions
diff --git a/src/test/rgw/test_rgw_kms.cc b/src/test/rgw/test_rgw_kms.cc
new file mode 100644
index 000000000..c1e36ad21
--- /dev/null
+++ b/src/test/rgw/test_rgw_kms.cc
@@ -0,0 +1,282 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include "common/ceph_context.h"
+#include "rgw/rgw_common.h"
+#define FORTEST_VIRTUAL virtual
+#include "rgw/rgw_kms.cc"
+
+using ::testing::_;
+using ::testing::Action;
+using ::testing::ActionInterface;
+using ::testing::MakeAction;
+using ::testing::StrEq;
+
+
+class MockTransitSecretEngine : public TransitSecretEngine {
+
+public:
+ MockTransitSecretEngine(CephContext *cct, EngineParmMap parms) : TransitSecretEngine(cct, parms){}
+
+ MOCK_METHOD(int, send_request, (const char *method, std::string_view infix, std::string_view key_id, const std::string& postdata, bufferlist &bl), (override));
+
+};
+
+class MockKvSecretEngine : public KvSecretEngine {
+
+public:
+ MockKvSecretEngine(CephContext *cct, EngineParmMap parms) : KvSecretEngine(cct, parms){}
+
+ MOCK_METHOD(int, send_request, (const char *method, std::string_view infix, std::string_view key_id, const std::string& postdata, bufferlist &bl), (override));
+
+};
+
+class TestSSEKMS : public ::testing::Test {
+
+protected:
+ CephContext *cct;
+ MockTransitSecretEngine* old_engine;
+ MockKvSecretEngine* kv_engine;
+ MockTransitSecretEngine* transit_engine;
+
+ void SetUp() override {
+ EngineParmMap old_parms, kv_parms, new_parms;
+ cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
+ old_parms["compat"] = "2";
+ old_engine = new MockTransitSecretEngine(cct, std::move(old_parms));
+ kv_engine = new MockKvSecretEngine(cct, std::move(kv_parms));
+ new_parms["compat"] = "1";
+ transit_engine = new MockTransitSecretEngine(cct, std::move(new_parms));
+ }
+
+ void TearDown() {
+ delete old_engine;
+ delete kv_engine;
+ delete transit_engine;
+ }
+
+};
+
+
+TEST_F(TestSSEKMS, vault_token_file_unset)
+{
+ cct->_conf.set_val("rgw_crypt_vault_auth", "token");
+ EngineParmMap old_parms, kv_parms;
+ TransitSecretEngine te(cct, std::move(old_parms));
+ KvSecretEngine kv(cct, std::move(kv_parms));
+
+ std::string_view key_id("my_key");
+ std::string actual_key;
+
+ ASSERT_EQ(te.get_key(key_id, actual_key), -EINVAL);
+ ASSERT_EQ(kv.get_key(key_id, actual_key), -EINVAL);
+}
+
+
+TEST_F(TestSSEKMS, non_existent_vault_token_file)
+{
+ cct->_conf.set_val("rgw_crypt_vault_auth", "token");
+ cct->_conf.set_val("rgw_crypt_vault_token_file", "/nonexistent/file");
+ EngineParmMap old_parms, kv_parms;
+ TransitSecretEngine te(cct, std::move(old_parms));
+ KvSecretEngine kv(cct, std::move(kv_parms));
+
+ std::string_view key_id("my_key/1");
+ std::string actual_key;
+
+ ASSERT_EQ(te.get_key(key_id, actual_key), -ENOENT);
+ ASSERT_EQ(kv.get_key(key_id, actual_key), -ENOENT);
+}
+
+
+typedef int SendRequestMethod(const char *,
+ std::string_view, std::string_view,
+ const std::string &, bufferlist &);
+
+class SetPointedValueAction : public ActionInterface<SendRequestMethod> {
+ public:
+ std::string json;
+
+ SetPointedValueAction(std::string json){
+ this->json = json;
+ }
+
+ int Perform(const ::std::tuple<const char *, std::string_view, std::string_view, const std::string &, bufferlist &>& args) override {
+// const char *method = ::std::get<0>(args);
+// std::string_view infix = ::std::get<1>(args);
+// std::string_view key_id = ::std::get<2>(args);
+// const std::string& postdata = ::std::get<3>(args);
+ bufferlist& bl = ::std::get<4>(args);
+
+// std::cout << "method = " << method << " infix = " << infix << " key_id = " << key_id
+// << " postdata = " << postdata
+// << " => json = " << json
+// << std::endl;
+
+ bl.append(json);
+ // note: in the bufferlist, the string is not
+ // necessarily 0 terminated at this point. Logic in
+ // rgw_kms.cc must handle this (by appending a 0.)
+ return 0;
+ }
+};
+
+Action<SendRequestMethod> SetPointedValue(std::string json) {
+ return MakeAction(new SetPointedValueAction(json));
+}
+
+
+TEST_F(TestSSEKMS, test_transit_key_version_extraction){
+ string json = R"({"data": {"keys": {"6": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
+ EXPECT_CALL(*old_engine, send_request(StrEq("GET"), StrEq(""), StrEq("1/2/3/4/5/6"), StrEq(""), _)).WillOnce(SetPointedValue(json));
+
+ std::string actual_key;
+ std::string tests[11] {"/", "my_key/", "my_key", "", "my_key/a", "my_key/1a",
+ "my_key/a1", "my_key/1a1", "my_key/1/a", "1", "my_key/1/"
+ };
+
+ int res;
+ for (const auto &test: tests) {
+ res = old_engine->get_key(std::string_view(test), actual_key);
+ ASSERT_EQ(res, -EINVAL);
+ }
+
+ res = old_engine->get_key(std::string_view("1/2/3/4/5/6"), actual_key);
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
+}
+
+
+TEST_F(TestSSEKMS, test_transit_backend){
+
+ std::string_view my_key("my_key/1");
+ std::string actual_key;
+
+ // Mocks the expected return Value from Vault Server using custom Argument Action
+ string json = R"({"data": {"keys": {"1": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
+ EXPECT_CALL(*old_engine, send_request(StrEq("GET"), StrEq(""), StrEq("my_key/1"), StrEq(""), _)).WillOnce(SetPointedValue(json));
+
+ int res = old_engine->get_key(my_key, actual_key);
+
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
+}
+
+
+TEST_F(TestSSEKMS, test_transit_makekey){
+
+ std::string_view my_key("my_key");
+ std::string actual_key;
+ map<string, bufferlist> attrs;
+
+ // Mocks the expected return Value from Vault Server using custom Argument Action
+ string post_json = R"({"data": {"ciphertext": "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ","key_version": 1,"plaintext": "3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="}})";
+ EXPECT_CALL(*transit_engine, send_request(StrEq("POST"), StrEq("/datakey/plaintext/"), StrEq("my_key"), _, _))
+ .WillOnce(SetPointedValue(post_json));
+
+ set_attr(attrs, RGW_ATTR_CRYPT_CONTEXT, R"({"aws:s3:arn": "fred"})");
+ set_attr(attrs, RGW_ATTR_CRYPT_KEYID, my_key);
+
+ int res = transit_engine->make_actual_key(attrs, actual_key);
+ std::string cipher_text { get_str_attribute(attrs,RGW_ATTR_CRYPT_DATAKEY) };
+
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
+ ASSERT_EQ(cipher_text, "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ");
+}
+
+TEST_F(TestSSEKMS, test_transit_reconstitutekey){
+
+ std::string_view my_key("my_key");
+ std::string actual_key;
+ map<string, bufferlist> attrs;
+
+ // Mocks the expected return Value from Vault Server using custom Argument Action
+ set_attr(attrs, RGW_ATTR_CRYPT_DATAKEY, "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ");
+ string post_json = R"({"data": {"key_version": 1,"plaintext": "3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="}})";
+ EXPECT_CALL(*transit_engine, send_request(StrEq("POST"), StrEq("/decrypt/"), StrEq("my_key"), _, _))
+ .WillOnce(SetPointedValue(post_json));
+
+ set_attr(attrs, RGW_ATTR_CRYPT_CONTEXT, R"({"aws:s3:arn": "fred"})");
+ set_attr(attrs, RGW_ATTR_CRYPT_KEYID, my_key);
+
+ int res = transit_engine->reconstitute_actual_key(attrs, actual_key);
+
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
+}
+
+TEST_F(TestSSEKMS, test_kv_backend){
+
+ std::string_view my_key("my_key");
+ std::string actual_key;
+
+ // Mocks the expected return value from Vault Server using custom Argument Action
+ string json = R"({"data": {"data": {"key": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
+ EXPECT_CALL(*kv_engine, send_request(StrEq("GET"), StrEq(""), StrEq("my_key"), StrEq(""), _))
+ .WillOnce(SetPointedValue(json));
+
+ int res = kv_engine->get_key(my_key, actual_key);
+
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
+}
+
+
+TEST_F(TestSSEKMS, concat_url)
+{
+ // Each test has 3 strings:
+ // * the base URL
+ // * the path we want to concatenate
+ // * the exepected final URL
+ std::string tests[9][3] ={
+ {"", "", ""},
+ {"", "bar", "/bar"},
+ {"", "/bar", "/bar"},
+ {"foo", "", "foo"},
+ {"foo", "bar", "foo/bar"},
+ {"foo", "/bar", "foo/bar"},
+ {"foo/", "", "foo/"},
+ {"foo/", "bar", "foo/bar"},
+ {"foo/", "/bar", "foo/bar"},
+ };
+ for (const auto &test: tests) {
+ std::string url(test[0]), path(test[1]), expected(test[2]);
+ concat_url(url, path);
+ ASSERT_EQ(url, expected);
+ }
+}
+
+
+TEST_F(TestSSEKMS, string_ends_maybe_slash)
+{
+ struct { std::string hay, needle; bool expected; } tests[] ={
+ {"jack here", "fred", false},
+ {"here is a fred", "fred", true},
+ {"and a fred/", "fred", true},
+ {"no fred here", "fred", false},
+ {"double fred//", "fred", true},
+ };
+ for (const auto &test: tests) {
+ bool expected { string_ends_maybe_slash(test.hay, test.needle) };
+ ASSERT_EQ(expected, test.expected);
+ }
+}
+
+
+TEST_F(TestSSEKMS, test_transit_backend_empty_response)
+{
+ std::string_view my_key("/key/nonexistent/1");
+ std::string actual_key;
+
+ // Mocks the expected return Value from Vault Server using custom Argument Action
+ string json = R"({"errors": ["version does not exist or cannot be found"]})";
+ EXPECT_CALL(*old_engine, send_request(StrEq("GET"), StrEq(""), StrEq("/key/nonexistent/1"), StrEq(""), _)).WillOnce(SetPointedValue(json));
+
+ int res = old_engine->get_key(my_key, actual_key);
+
+ ASSERT_EQ(res, -EINVAL);
+ ASSERT_EQ(actual_key, from_base64(""));
+}