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