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/rgw_common.h"
8 #define FORTEST_VIRTUAL virtual
9 #include "rgw/rgw_kms.cc"
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
, EngineParmMap parms
) : TransitSecretEngine(cct
, 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
, EngineParmMap parms
) : KvSecretEngine(cct
, 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 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
));
57 delete transit_engine
;
63 TEST_F(TestSSEKMS
, vault_token_file_unset
)
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);
71 std::string_view
key_id("my_key");
72 std::string actual_key
;
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
);
79 TEST_F(TestSSEKMS
, non_existent_vault_token_file
)
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);
88 std::string_view
key_id("my_key/1");
89 std::string actual_key
;
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
);
96 typedef int SendRequestMethod(const DoutPrefixProvider
*dpp
, const char *,
97 std::string_view
, std::string_view
,
98 const std::string
&, bufferlist
&);
100 class SetPointedValueAction
: public ActionInterface
<SendRequestMethod
> {
104 SetPointedValueAction(std::string json
){
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
);
116 // std::cout << "method = " << method << " infix = " << infix << " key_id = " << key_id
117 // << " postdata = " << postdata
118 // << " => json = " << 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.)
129 Action
<SendRequestMethod
> SetPointedValue(std::string json
) {
130 return MakeAction(new SetPointedValueAction(json
));
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
));
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/"
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
);
150 res
= old_engine
->get_key(&no_dpp
, std::string_view("1/2/3/4/5/6"), actual_key
);
152 ASSERT_EQ(actual_key
, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
156 TEST_F(TestSSEKMS
, test_transit_backend
){
158 std::string_view
my_key("my_key/1");
159 std::string actual_key
;
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
));
166 int res
= old_engine
->get_key(&no_dpp
, my_key
, actual_key
);
169 ASSERT_EQ(actual_key
, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
173 TEST_F(TestSSEKMS
, test_transit_makekey
){
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);
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
));
185 set_attr(attrs
, RGW_ATTR_CRYPT_CONTEXT
, R
"({"aws
:s3
:arn
": "fred
"})");
186 set_attr(attrs
, RGW_ATTR_CRYPT_KEYID
, my_key
);
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
) };
192 ASSERT_EQ(actual_key
, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
193 ASSERT_EQ(cipher_text
, "vault:v2:HbdxLnUztGVo+RseCIaYVn/4wEUiJNT6GQfw57KXQmhXVe7i1/kgLWegEPg1I6lexhIuXAM6Q2YvY0aZ");
196 TEST_F(TestSSEKMS
, test_transit_reconstitutekey
){
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);
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
));
209 set_attr(attrs
, RGW_ATTR_CRYPT_CONTEXT
, R
"({"aws
:s3
:arn
": "fred
"})");
210 set_attr(attrs
, RGW_ATTR_CRYPT_KEYID
, my_key
);
212 int res
= transit_engine
->reconstitute_actual_key(&no_dpp
, attrs
, actual_key
);
215 ASSERT_EQ(actual_key
, from_base64("3xfTra/dsIf3TMa3mAT2IxPpM7YWm/NvUb4gDfSDX4g="));
218 TEST_F(TestSSEKMS
, test_kv_backend
){
220 std::string_view
my_key("my_key");
221 std::string actual_key
;
222 const NoDoutPrefix
no_dpp(cct
, 1);
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
));
229 int res
= kv_engine
->get_key(&no_dpp
, my_key
, actual_key
);
232 ASSERT_EQ(actual_key
, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
236 TEST_F(TestSSEKMS
, concat_url
)
238 // Each test has 3 strings:
240 // * the path we want to concatenate
241 // * the exepected final URL
242 std::string tests
[9][3] ={
245 {"", "/bar", "/bar"},
247 {"foo", "bar", "foo/bar"},
248 {"foo", "/bar", "foo/bar"},
249 {"foo/", "", "foo/"},
250 {"foo/", "bar", "foo/bar"},
251 {"foo/", "/bar", "foo/bar"},
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
);
261 TEST_F(TestSSEKMS
, string_ends_maybe_slash
)
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},
270 for (const auto &test
: tests
) {
271 bool expected
{ string_ends_maybe_slash(test
.hay
, test
.needle
) };
272 ASSERT_EQ(expected
, test
.expected
);
277 TEST_F(TestSSEKMS
, test_transit_backend_empty_response
)
279 std::string_view
my_key("/key/nonexistent/1");
280 std::string actual_key
;
281 const NoDoutPrefix
no_dpp(cct
, 1);
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
));
287 int res
= old_engine
->get_key(&no_dpp
, my_key
, actual_key
);
289 ASSERT_EQ(res
, -EINVAL
);
290 ASSERT_EQ(actual_key
, from_base64(""));