]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/rgw/test_rgw_kms.cc
update ceph source to reef 18.2.1
[ceph.git] / ceph / src / test / rgw / test_rgw_kms.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include <gtest/gtest.h>
5 #include <gmock/gmock.h>
6 #include "common/ceph_context.h"
7 #include "rgw_common.h"
8 #define FORTEST_VIRTUAL virtual
9 #include "rgw_kms.cc"
10
11 using ::testing::_;
12 using ::testing::Action;
13 using ::testing::ActionInterface;
14 using ::testing::MakeAction;
15 using ::testing::StrEq;
16
17
18 class MockTransitSecretEngine : public TransitSecretEngine {
19
20 public:
21 MockTransitSecretEngine(CephContext *cct, SSEContext & kctx, EngineParmMap parms) : TransitSecretEngine(cct, kctx, parms){}
22
23 MOCK_METHOD(int, send_request, (const DoutPrefixProvider *dpp, const char *method, std::string_view infix, std::string_view key_id, const std::string& postdata, bufferlist &bl), (override));
24
25 };
26
27 class MockKvSecretEngine : public KvSecretEngine {
28
29 public:
30 MockKvSecretEngine(CephContext *cct, SSEContext & kctx, EngineParmMap parms) : KvSecretEngine(cct, kctx, parms){}
31
32 MOCK_METHOD(int, send_request, (const DoutPrefixProvider *dpp, const char *method, std::string_view infix, std::string_view key_id, const std::string& postdata, bufferlist &bl), (override));
33
34 };
35
36 class TestSSEKMS : public ::testing::Test {
37
38 protected:
39 CephContext *cct;
40 MockTransitSecretEngine* old_engine;
41 MockKvSecretEngine* kv_engine;
42 MockTransitSecretEngine* transit_engine;
43
44 void SetUp() override {
45 EngineParmMap old_parms, kv_parms, new_parms;
46 cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
47 KMSContext kctx { cct };
48 old_parms["compat"] = "2";
49 old_engine = new MockTransitSecretEngine(cct, kctx, std::move(old_parms));
50 kv_engine = new MockKvSecretEngine(cct, kctx, std::move(kv_parms));
51 new_parms["compat"] = "1";
52 transit_engine = new MockTransitSecretEngine(cct, kctx, std::move(new_parms));
53 }
54
55 void TearDown() {
56 delete old_engine;
57 delete kv_engine;
58 delete transit_engine;
59 }
60
61 };
62
63
64 TEST_F(TestSSEKMS, vault_token_file_unset)
65 {
66 cct->_conf.set_val("rgw_crypt_vault_auth", "token");
67 EngineParmMap old_parms, kv_parms;
68 KMSContext kctx { cct };
69 TransitSecretEngine te(cct, kctx, std::move(old_parms));
70 KvSecretEngine kv(cct, kctx, std::move(kv_parms));
71 const NoDoutPrefix no_dpp(cct, 1);
72
73 std::string_view key_id("my_key");
74 std::string actual_key;
75
76 ASSERT_EQ(te.get_key(&no_dpp, key_id, actual_key), -EINVAL);
77 ASSERT_EQ(kv.get_key(&no_dpp, key_id, actual_key), -EINVAL);
78 }
79
80
81 TEST_F(TestSSEKMS, non_existent_vault_token_file)
82 {
83 cct->_conf.set_val("rgw_crypt_vault_auth", "token");
84 cct->_conf.set_val("rgw_crypt_vault_token_file", "/nonexistent/file");
85 EngineParmMap old_parms, kv_parms;
86 KMSContext kctx { cct };
87 TransitSecretEngine te(cct, kctx, std::move(old_parms));
88 KvSecretEngine kv(cct, kctx, std::move(kv_parms));
89 const NoDoutPrefix no_dpp(cct, 1);
90
91 std::string_view key_id("my_key/1");
92 std::string actual_key;
93
94 ASSERT_EQ(te.get_key(&no_dpp, key_id, actual_key), -ENOENT);
95 ASSERT_EQ(kv.get_key(&no_dpp, key_id, actual_key), -ENOENT);
96 }
97
98
99 typedef int SendRequestMethod(const DoutPrefixProvider *dpp, const char *,
100 std::string_view, std::string_view,
101 const std::string &, bufferlist &);
102
103 class SetPointedValueAction : public ActionInterface<SendRequestMethod> {
104 public:
105 std::string json;
106
107 SetPointedValueAction(std::string json){
108 this->json = json;
109 }
110
111 int Perform(const ::std::tuple<const DoutPrefixProvider*, const char *, std::string_view, std::string_view, const std::string &, bufferlist &>& args) override {
112 // const DoutPrefixProvider *dpp = ::std::get<0>(args);
113 // const char *method = ::std::get<1>(args);
114 // std::string_view infix = ::std::get<2>(args);
115 // std::string_view key_id = ::std::get<3>(args);
116 // const std::string& postdata = ::std::get<4>(args);
117 bufferlist& bl = ::std::get<5>(args);
118
119 // std::cout << "method = " << method << " infix = " << infix << " key_id = " << key_id
120 // << " postdata = " << postdata
121 // << " => json = " << json
122 // << std::endl;
123
124 bl.append(json);
125 // note: in the bufferlist, the string is not
126 // necessarily 0 terminated at this point. Logic in
127 // rgw_kms.cc must handle this (by appending a 0.)
128 return 0;
129 }
130 };
131
132 Action<SendRequestMethod> SetPointedValue(std::string json) {
133 return MakeAction(new SetPointedValueAction(json));
134 }
135
136
137 TEST_F(TestSSEKMS, test_transit_key_version_extraction){
138 const NoDoutPrefix no_dpp(cct, 1);
139 string json = R"({"data": {"keys": {"6": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
140 EXPECT_CALL(*old_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("1/2/3/4/5/6"), StrEq(""), _)).WillOnce(SetPointedValue(json));
141
142 std::string actual_key;
143 std::string tests[11] {"/", "my_key/", "my_key", "", "my_key/a", "my_key/1a",
144 "my_key/a1", "my_key/1a1", "my_key/1/a", "1", "my_key/1/"
145 };
146
147 int res;
148 for (const auto &test: tests) {
149 res = old_engine->get_key(&no_dpp, std::string_view(test), actual_key);
150 ASSERT_EQ(res, -EINVAL);
151 }
152
153 res = old_engine->get_key(&no_dpp, std::string_view("1/2/3/4/5/6"), actual_key);
154 ASSERT_EQ(res, 0);
155 ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
156 }
157
158
159 TEST_F(TestSSEKMS, test_transit_backend){
160
161 std::string_view my_key("my_key/1");
162 std::string actual_key;
163
164 // Mocks the expected return Value from Vault Server using custom Argument Action
165 string json = R"({"data": {"keys": {"1": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
166 const NoDoutPrefix no_dpp(cct, 1);
167 EXPECT_CALL(*old_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("my_key/1"), StrEq(""), _)).WillOnce(SetPointedValue(json));
168
169 int res = old_engine->get_key(&no_dpp, my_key, actual_key);
170
171 ASSERT_EQ(res, 0);
172 ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
173 }
174
175
176 TEST_F(TestSSEKMS, test_transit_makekey){
177
178 std::string_view my_key("my_key");
179 std::string actual_key;
180 map<string, bufferlist> attrs;
181 const NoDoutPrefix no_dpp(cct, 1);
182
183 // Mocks the expected return Value from Vault Server using custom Argument Action
184 string post_json = R"({"data": {"ciphertext": "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ","key_version": 1,"plaintext": "3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="}})";
185 EXPECT_CALL(*transit_engine, send_request(&no_dpp, StrEq("POST"), StrEq("/datakey/plaintext/"), StrEq("my_key"), _, _))
186 .WillOnce(SetPointedValue(post_json));
187
188 set_attr(attrs, RGW_ATTR_CRYPT_CONTEXT, R"({"aws:s3:arn": "fred"})");
189 set_attr(attrs, RGW_ATTR_CRYPT_KEYID, my_key);
190
191 int res = transit_engine->make_actual_key(&no_dpp, attrs, actual_key);
192 std::string cipher_text { get_str_attribute(attrs,RGW_ATTR_CRYPT_DATAKEY) };
193
194 ASSERT_EQ(res, 0);
195 ASSERT_EQ(actual_key, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
196 ASSERT_EQ(cipher_text, "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ");
197 }
198
199 TEST_F(TestSSEKMS, test_transit_reconstitutekey){
200
201 std::string_view my_key("my_key");
202 std::string actual_key;
203 map<string, bufferlist> attrs;
204 const NoDoutPrefix no_dpp(cct, 1);
205
206 // Mocks the expected return Value from Vault Server using custom Argument Action
207 set_attr(attrs, RGW_ATTR_CRYPT_DATAKEY, "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ");
208 string post_json = R"({"data": {"key_version": 1,"plaintext": "3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="}})";
209 EXPECT_CALL(*transit_engine, send_request(&no_dpp, StrEq("POST"), StrEq("/decrypt/"), StrEq("my_key"), _, _))
210 .WillOnce(SetPointedValue(post_json));
211
212 set_attr(attrs, RGW_ATTR_CRYPT_CONTEXT, R"({"aws:s3:arn": "fred"})");
213 set_attr(attrs, RGW_ATTR_CRYPT_KEYID, my_key);
214
215 int res = transit_engine->reconstitute_actual_key(&no_dpp, attrs, actual_key);
216
217 ASSERT_EQ(res, 0);
218 ASSERT_EQ(actual_key, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
219 }
220
221 TEST_F(TestSSEKMS, test_kv_backend){
222
223 std::string_view my_key("my_key");
224 std::string actual_key;
225 const NoDoutPrefix no_dpp(cct, 1);
226
227 // Mocks the expected return value from Vault Server using custom Argument Action
228 string json = R"({"data": {"data": {"key": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
229 EXPECT_CALL(*kv_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("my_key"), StrEq(""), _))
230 .WillOnce(SetPointedValue(json));
231
232 int res = kv_engine->get_key(&no_dpp, my_key, actual_key);
233
234 ASSERT_EQ(res, 0);
235 ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
236 }
237
238
239 TEST_F(TestSSEKMS, concat_url)
240 {
241 // Each test has 3 strings:
242 // * the base URL
243 // * the path we want to concatenate
244 // * the exepected final URL
245 std::string tests[9][3] ={
246 {"", "", ""},
247 {"", "bar", "/bar"},
248 {"", "/bar", "/bar"},
249 {"foo", "", "foo"},
250 {"foo", "bar", "foo/bar"},
251 {"foo", "/bar", "foo/bar"},
252 {"foo/", "", "foo/"},
253 {"foo/", "bar", "foo/bar"},
254 {"foo/", "/bar", "foo/bar"},
255 };
256 for (const auto &test: tests) {
257 std::string url(test[0]), path(test[1]), expected(test[2]);
258 concat_url(url, path);
259 ASSERT_EQ(url, expected);
260 }
261 }
262
263
264 TEST_F(TestSSEKMS, string_ends_maybe_slash)
265 {
266 struct { std::string hay, needle; bool expected; } tests[] ={
267 {"jack here", "fred", false},
268 {"here is a fred", "fred", true},
269 {"and a fred/", "fred", true},
270 {"no fred here", "fred", false},
271 {"double fred//", "fred", true},
272 };
273 for (const auto &test: tests) {
274 bool expected { string_ends_maybe_slash(test.hay, test.needle) };
275 ASSERT_EQ(expected, test.expected);
276 }
277 }
278
279
280 TEST_F(TestSSEKMS, test_transit_backend_empty_response)
281 {
282 std::string_view my_key("/key/nonexistent/1");
283 std::string actual_key;
284 const NoDoutPrefix no_dpp(cct, 1);
285
286 // Mocks the expected return Value from Vault Server using custom Argument Action
287 string json = R"({"errors": ["version does not exist or cannot be found"]})";
288 EXPECT_CALL(*old_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("/key/nonexistent/1"), StrEq(""), _)).WillOnce(SetPointedValue(json));
289
290 int res = old_engine->get_key(&no_dpp, my_key, actual_key);
291
292 ASSERT_EQ(res, -EINVAL);
293 ASSERT_EQ(actual_key, from_base64(""));
294 }