]>
Commit | Line | Data |
---|---|---|
9f95a23c TL |
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/rgw_common.h" | |
f67539c2 | 8 | #define FORTEST_VIRTUAL virtual |
9f95a23c TL |
9 | #include "rgw/rgw_kms.cc" |
10 | ||
11 | using ::testing::_; | |
12 | using ::testing::Action; | |
13 | using ::testing::ActionInterface; | |
14 | using ::testing::MakeAction; | |
f67539c2 | 15 | using ::testing::StrEq; |
9f95a23c TL |
16 | |
17 | ||
18 | class MockTransitSecretEngine : public TransitSecretEngine { | |
19 | ||
20 | public: | |
2a845540 | 21 | MockTransitSecretEngine(CephContext *cct, SSEContext & kctx, EngineParmMap parms) : TransitSecretEngine(cct, kctx, parms){} |
9f95a23c | 22 | |
20effc67 | 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)); |
9f95a23c TL |
24 | |
25 | }; | |
26 | ||
27 | class MockKvSecretEngine : public KvSecretEngine { | |
28 | ||
29 | public: | |
2a845540 | 30 | MockKvSecretEngine(CephContext *cct, SSEContext & kctx, EngineParmMap parms) : KvSecretEngine(cct, kctx, parms){} |
9f95a23c | 31 | |
20effc67 | 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)); |
9f95a23c TL |
33 | |
34 | }; | |
35 | ||
36 | class TestSSEKMS : public ::testing::Test { | |
37 | ||
38 | protected: | |
39 | CephContext *cct; | |
f67539c2 | 40 | MockTransitSecretEngine* old_engine; |
9f95a23c | 41 | MockKvSecretEngine* kv_engine; |
f67539c2 | 42 | MockTransitSecretEngine* transit_engine; |
9f95a23c TL |
43 | |
44 | void SetUp() override { | |
f67539c2 | 45 | EngineParmMap old_parms, kv_parms, new_parms; |
9f95a23c | 46 | cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get(); |
2a845540 | 47 | KMSContext kctx { cct }; |
f67539c2 | 48 | old_parms["compat"] = "2"; |
2a845540 TL |
49 | old_engine = new MockTransitSecretEngine(cct, kctx, std::move(old_parms)); |
50 | kv_engine = new MockKvSecretEngine(cct, kctx, std::move(kv_parms)); | |
f67539c2 | 51 | new_parms["compat"] = "1"; |
2a845540 | 52 | transit_engine = new MockTransitSecretEngine(cct, kctx, std::move(new_parms)); |
9f95a23c TL |
53 | } |
54 | ||
55 | void TearDown() { | |
f67539c2 | 56 | delete old_engine; |
9f95a23c | 57 | delete kv_engine; |
f67539c2 | 58 | delete transit_engine; |
9f95a23c TL |
59 | } |
60 | ||
61 | }; | |
62 | ||
63 | ||
64 | TEST_F(TestSSEKMS, vault_token_file_unset) | |
65 | { | |
66 | cct->_conf.set_val("rgw_crypt_vault_auth", "token"); | |
f67539c2 | 67 | EngineParmMap old_parms, kv_parms; |
2a845540 TL |
68 | KMSContext kctx { cct }; |
69 | TransitSecretEngine te(cct, kctx, std::move(old_parms)); | |
70 | KvSecretEngine kv(cct, kctx, std::move(kv_parms)); | |
20effc67 | 71 | const NoDoutPrefix no_dpp(cct, 1); |
9f95a23c | 72 | |
f67539c2 | 73 | std::string_view key_id("my_key"); |
9f95a23c TL |
74 | std::string actual_key; |
75 | ||
20effc67 TL |
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); | |
9f95a23c TL |
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"); | |
f67539c2 | 85 | EngineParmMap old_parms, kv_parms; |
2a845540 TL |
86 | KMSContext kctx { cct }; |
87 | TransitSecretEngine te(cct, kctx, std::move(old_parms)); | |
88 | KvSecretEngine kv(cct, kctx, std::move(kv_parms)); | |
20effc67 | 89 | const NoDoutPrefix no_dpp(cct, 1); |
9f95a23c | 90 | |
f67539c2 | 91 | std::string_view key_id("my_key/1"); |
9f95a23c TL |
92 | std::string actual_key; |
93 | ||
20effc67 TL |
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); | |
9f95a23c TL |
96 | } |
97 | ||
98 | ||
20effc67 | 99 | typedef int SendRequestMethod(const DoutPrefixProvider *dpp, const char *, |
f67539c2 TL |
100 | std::string_view, std::string_view, |
101 | const std::string &, bufferlist &); | |
9f95a23c TL |
102 | |
103 | class SetPointedValueAction : public ActionInterface<SendRequestMethod> { | |
104 | public: | |
f67539c2 | 105 | std::string json; |
9f95a23c TL |
106 | |
107 | SetPointedValueAction(std::string json){ | |
108 | this->json = json; | |
109 | } | |
110 | ||
20effc67 TL |
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); | |
f67539c2 TL |
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.) | |
9f95a23c TL |
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){ | |
20effc67 | 138 | const NoDoutPrefix no_dpp(cct, 1); |
9f95a23c | 139 | string json = R"({"data": {"keys": {"6": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})"; |
20effc67 | 140 | EXPECT_CALL(*old_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("1/2/3/4/5/6"), StrEq(""), _)).WillOnce(SetPointedValue(json)); |
9f95a23c TL |
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) { | |
20effc67 | 149 | res = old_engine->get_key(&no_dpp, std::string_view(test), actual_key); |
9f95a23c TL |
150 | ASSERT_EQ(res, -EINVAL); |
151 | } | |
152 | ||
20effc67 | 153 | res = old_engine->get_key(&no_dpp, std::string_view("1/2/3/4/5/6"), actual_key); |
9f95a23c TL |
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 | ||
f67539c2 | 161 | std::string_view my_key("my_key/1"); |
9f95a23c TL |
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="}}})"; | |
20effc67 TL |
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)); | |
9f95a23c | 168 | |
20effc67 | 169 | int res = old_engine->get_key(&no_dpp, my_key, actual_key); |
9f95a23c TL |
170 | |
171 | ASSERT_EQ(res, 0); | |
172 | ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg=")); | |
173 | } | |
174 | ||
175 | ||
f67539c2 TL |
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; | |
20effc67 | 181 | const NoDoutPrefix no_dpp(cct, 1); |
f67539c2 TL |
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="}})"; | |
20effc67 | 185 | EXPECT_CALL(*transit_engine, send_request(&no_dpp, StrEq("POST"), StrEq("/datakey/plaintext/"), StrEq("my_key"), _, _)) |
f67539c2 TL |
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 | ||
20effc67 | 191 | int res = transit_engine->make_actual_key(&no_dpp, attrs, actual_key); |
f67539c2 TL |
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; | |
20effc67 | 204 | const NoDoutPrefix no_dpp(cct, 1); |
f67539c2 TL |
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="}})"; | |
20effc67 | 209 | EXPECT_CALL(*transit_engine, send_request(&no_dpp, StrEq("POST"), StrEq("/decrypt/"), StrEq("my_key"), _, _)) |
f67539c2 TL |
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 | ||
20effc67 | 215 | int res = transit_engine->reconstitute_actual_key(&no_dpp, attrs, actual_key); |
f67539c2 TL |
216 | |
217 | ASSERT_EQ(res, 0); | |
218 | ASSERT_EQ(actual_key, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g=")); | |
219 | } | |
220 | ||
9f95a23c TL |
221 | TEST_F(TestSSEKMS, test_kv_backend){ |
222 | ||
f67539c2 | 223 | std::string_view my_key("my_key"); |
9f95a23c | 224 | std::string actual_key; |
20effc67 | 225 | const NoDoutPrefix no_dpp(cct, 1); |
9f95a23c TL |
226 | |
227 | // Mocks the expected return value from Vault Server using custom Argument Action | |
228 | string json = R"({"data": {"data": {"key": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})"; | |
20effc67 | 229 | EXPECT_CALL(*kv_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("my_key"), StrEq(""), _)) |
f67539c2 | 230 | .WillOnce(SetPointedValue(json)); |
9f95a23c | 231 | |
20effc67 | 232 | int res = kv_engine->get_key(&no_dpp, my_key, actual_key); |
9f95a23c TL |
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 | ||
f67539c2 TL |
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 | ||
9f95a23c TL |
280 | TEST_F(TestSSEKMS, test_transit_backend_empty_response) |
281 | { | |
f67539c2 | 282 | std::string_view my_key("/key/nonexistent/1"); |
9f95a23c | 283 | std::string actual_key; |
20effc67 | 284 | const NoDoutPrefix no_dpp(cct, 1); |
9f95a23c TL |
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"]})"; | |
20effc67 | 288 | EXPECT_CALL(*old_engine, send_request(&no_dpp, StrEq("GET"), StrEq(""), StrEq("/key/nonexistent/1"), StrEq(""), _)).WillOnce(SetPointedValue(json)); |
9f95a23c | 289 | |
20effc67 | 290 | int res = old_engine->get_key(&no_dpp, my_key, actual_key); |
9f95a23c TL |
291 | |
292 | ASSERT_EQ(res, -EINVAL); | |
293 | ASSERT_EQ(actual_key, from_base64("")); | |
294 | } |