]> git.proxmox.com Git - ceph.git/blame - ceph/src/test/rgw/test_rgw_kms.cc
import ceph quincy 17.2.4
[ceph.git] / ceph / src / test / rgw / test_rgw_kms.cc
CommitLineData
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
11using ::testing::_;
12using ::testing::Action;
13using ::testing::ActionInterface;
14using ::testing::MakeAction;
f67539c2 15using ::testing::StrEq;
9f95a23c
TL
16
17
18class MockTransitSecretEngine : public TransitSecretEngine {
19
20public:
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
27class MockKvSecretEngine : public KvSecretEngine {
28
29public:
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
36class TestSSEKMS : public ::testing::Test {
37
38protected:
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
64TEST_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
81TEST_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 99typedef int SendRequestMethod(const DoutPrefixProvider *dpp, const char *,
f67539c2
TL
100 std::string_view, std::string_view,
101 const std::string &, bufferlist &);
9f95a23c
TL
102
103class 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
132Action<SendRequestMethod> SetPointedValue(std::string json) {
133 return MakeAction(new SetPointedValueAction(json));
134}
135
136
137TEST_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
159TEST_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
176TEST_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
199TEST_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
221TEST_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
239TEST_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
264TEST_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
280TEST_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}