1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
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
12 using ::testing::Action
;
13 using ::testing::ActionInterface
;
14 using ::testing::MakeAction
;
15 using ::testing::StrEq
;
18 class MockTransitSecretEngine
: public TransitSecretEngine
{
21 MockTransitSecretEngine(CephContext
*cct
, SSEContext
& kctx
, EngineParmMap parms
) : TransitSecretEngine(cct
, kctx
, parms
){}
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
));
27 class MockKvSecretEngine
: public KvSecretEngine
{
30 MockKvSecretEngine(CephContext
*cct
, SSEContext
& kctx
, EngineParmMap parms
) : KvSecretEngine(cct
, kctx
, parms
){}
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
));
36 class TestSSEKMS
: public ::testing::Test
{
40 MockTransitSecretEngine
* old_engine
;
41 MockKvSecretEngine
* kv_engine
;
42 MockTransitSecretEngine
* transit_engine
;
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
));
58 delete transit_engine
;
64 TEST_F(TestSSEKMS
, vault_token_file_unset
)
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);
73 std::string_view
key_id("my_key");
74 std::string actual_key
;
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
);
81 TEST_F(TestSSEKMS
, non_existent_vault_token_file
)
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);
91 std::string_view
key_id("my_key/1");
92 std::string actual_key
;
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
);
99 typedef int SendRequestMethod(const DoutPrefixProvider
*dpp
, const char *,
100 std::string_view
, std::string_view
,
101 const std::string
&, bufferlist
&);
103 class SetPointedValueAction
: public ActionInterface
<SendRequestMethod
> {
107 SetPointedValueAction(std::string json
){
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
);
119 // std::cout << "method = " << method << " infix = " << infix << " key_id = " << key_id
120 // << " postdata = " << postdata
121 // << " => json = " << 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.)
132 Action
<SendRequestMethod
> SetPointedValue(std::string json
) {
133 return MakeAction(new SetPointedValueAction(json
));
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
));
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/"
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
);
153 res
= old_engine
->get_key(&no_dpp
, std::string_view("1/2/3/4/5/6"), actual_key
);
155 ASSERT_EQ(actual_key
, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
159 TEST_F(TestSSEKMS
, test_transit_backend
){
161 std::string_view
my_key("my_key/1");
162 std::string actual_key
;
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
));
169 int res
= old_engine
->get_key(&no_dpp
, my_key
, actual_key
);
172 ASSERT_EQ(actual_key
, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
176 TEST_F(TestSSEKMS
, test_transit_makekey
){
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);
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
));
188 set_attr(attrs
, RGW_ATTR_CRYPT_CONTEXT
, R
"({"aws
:s3
:arn
": "fred
"})");
189 set_attr(attrs
, RGW_ATTR_CRYPT_KEYID
, my_key
);
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
) };
195 ASSERT_EQ(actual_key
, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
196 ASSERT_EQ(cipher_text
, "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ");
199 TEST_F(TestSSEKMS
, test_transit_reconstitutekey
){
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);
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
));
212 set_attr(attrs
, RGW_ATTR_CRYPT_CONTEXT
, R
"({"aws
:s3
:arn
": "fred
"})");
213 set_attr(attrs
, RGW_ATTR_CRYPT_KEYID
, my_key
);
215 int res
= transit_engine
->reconstitute_actual_key(&no_dpp
, attrs
, actual_key
);
218 ASSERT_EQ(actual_key
, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
221 TEST_F(TestSSEKMS
, test_kv_backend
){
223 std::string_view
my_key("my_key");
224 std::string actual_key
;
225 const NoDoutPrefix
no_dpp(cct
, 1);
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
));
232 int res
= kv_engine
->get_key(&no_dpp
, my_key
, actual_key
);
235 ASSERT_EQ(actual_key
, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
239 TEST_F(TestSSEKMS
, concat_url
)
241 // Each test has 3 strings:
243 // * the path we want to concatenate
244 // * the exepected final URL
245 std::string tests
[9][3] ={
248 {"", "/bar", "/bar"},
250 {"foo", "bar", "foo/bar"},
251 {"foo", "/bar", "foo/bar"},
252 {"foo/", "", "foo/"},
253 {"foo/", "bar", "foo/bar"},
254 {"foo/", "/bar", "foo/bar"},
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
);
264 TEST_F(TestSSEKMS
, string_ends_maybe_slash
)
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},
273 for (const auto &test
: tests
) {
274 bool expected
{ string_ends_maybe_slash(test
.hay
, test
.needle
) };
275 ASSERT_EQ(expected
, test
.expected
);
280 TEST_F(TestSSEKMS
, test_transit_backend_empty_response
)
282 std::string_view
my_key("/key/nonexistent/1");
283 std::string actual_key
;
284 const NoDoutPrefix
no_dpp(cct
, 1);
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
));
290 int res
= old_engine
->get_key(&no_dpp
, my_key
, actual_key
);
292 ASSERT_EQ(res
, -EINVAL
);
293 ASSERT_EQ(actual_key
, from_base64(""));