1 #include "estoraged_test.hpp"
2 
3 #include "estoraged.hpp"
4 
5 #include <unistd.h>
6 
7 #include <boost/asio/io_context.hpp>
8 #include <sdbusplus/asio/connection.hpp>
9 #include <sdbusplus/asio/object_server.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 #include <xyz/openbmc_project/Inventory/Item/Volume/client.hpp>
12 
13 #include <exception>
14 #include <filesystem>
15 #include <fstream>
16 #include <iterator>
17 #include <memory>
18 #include <string>
19 #include <vector>
20 
21 #include <gmock/gmock.h>
22 #include <gtest/gtest.h>
23 
24 namespace estoraged_test
25 {
26 
27 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
28 using sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound;
29 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
30 using std::filesystem::path;
31 using ::testing::_;
32 using ::testing::ContainsRegex;
33 using ::testing::Return;
34 using ::testing::StrEq;
35 
36 class EStoragedTest : public testing::Test
37 {
38   public:
39     const char* testFileName = "testfile";
40     const char* testLuksDevName = "testfile_luksDev";
41     const uint64_t testSize = 24;
42     std::ofstream testFile;
43     const char* testPath = "/test/openbmc_project/storage/test_dev";
44     const char* estoragedInterface =
45         "xyz.openbmc_project.Inventory.Item.Volume";
46     const char* driveInterface = "xyz.openbmc_project.Inventory.Item.Drive";
47     std::string passwordString;
48     std::vector<uint8_t> password;
49     MockCryptsetupInterface* mockCryptIface{};
50     MockFilesystemInterface* mockFsIface{};
51     boost::asio::io_context io;
52     std::shared_ptr<sdbusplus::asio::connection> conn;
53     std::unique_ptr<sdbusplus::asio::object_server> objectServer;
54     std::unique_ptr<estoraged::EStoraged> esObject;
55 
56     EStoragedTest() :
57         passwordString("password"),
58         password(passwordString.begin(), passwordString.end())
59     {}
60 
61     void SetUp() override
62     {
63         /* Create an empty file that we'll pretend is a 'storage device'. */
64         testFile.open(testFileName,
65                       std::ios::out | std::ios::binary | std::ios::trunc);
66         testFile.close();
67         if (testFile.fail())
68         {
69             throw std::runtime_error("Failed to open test file");
70         }
71 
72         std::unique_ptr<MockCryptsetupInterface> cryptIface =
73             std::make_unique<MockCryptsetupInterface>();
74         mockCryptIface = cryptIface.get();
75         std::unique_ptr<MockFilesystemInterface> fsIface =
76             std::make_unique<MockFilesystemInterface>();
77         mockFsIface = fsIface.get();
78 
79         conn = std::make_shared<sdbusplus::asio::connection>(io);
80         // request D-Bus server name.
81         conn->request_name("xyz.openbmc_project.eStoraged.test");
82         objectServer = std::make_unique<sdbusplus::asio::object_server>(conn);
83 
84         esObject = std::make_unique<estoraged::EStoraged>(
85             *objectServer, testFileName, testLuksDevName, testSize,
86             std::move(cryptIface), std::move(fsIface));
87     }
88 
89     void TearDown() override
90     {
91         EXPECT_EQ(0, unlink(testFileName));
92     }
93 };
94 
95 /* Test case to format and then lock the LUKS device. */
96 TEST_F(EStoragedTest, FormatPass)
97 {
98     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
99 
100     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
101         .Times(1);
102 
103     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
104 
105     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
106         .Times(1);
107 
108     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
109 
110     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
111         .WillOnce(Return(false));
112 
113     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
114         .WillOnce(Return(true));
115 
116     EXPECT_CALL(*mockFsIface,
117                 doMount(ContainsRegex("/dev/mapper/"),
118                         StrEq(esObject->getMountPoint()), _, _, _))
119         .WillOnce(Return(0));
120 
121     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
122         .WillOnce(Return(0));
123 
124     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
125         .WillOnce(Return(true));
126 
127     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _)).Times(1);
128 
129     /* Format the encrypted device. */
130     esObject->formatLuks(password, Volume::FilesystemType::ext4);
131     EXPECT_FALSE(esObject->isLocked());
132 
133     esObject->lock();
134     EXPECT_TRUE(esObject->isLocked());
135 }
136 
137 /*
138  * Test case where the mount point directory already exists, so it shouldn't
139  * try to create it.
140  */
141 TEST_F(EStoragedTest, MountPointExistsPass)
142 {
143     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
144 
145     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
146         .Times(1);
147 
148     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
149 
150     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
151         .Times(1);
152 
153     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
154 
155     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
156         .WillOnce(Return(true));
157 
158     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
159         .Times(0);
160 
161     EXPECT_CALL(*mockFsIface,
162                 doMount(ContainsRegex("/dev/mapper/"),
163                         StrEq(esObject->getMountPoint()), _, _, _))
164         .WillOnce(Return(0));
165 
166     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
167         .WillOnce(Return(0));
168 
169     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
170         .WillOnce(Return(true));
171 
172     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _)).Times(1);
173 
174     /* Format the encrypted device. */
175     esObject->formatLuks(password, Volume::FilesystemType::ext4);
176     EXPECT_FALSE(esObject->isLocked());
177 
178     esObject->lock();
179     EXPECT_TRUE(esObject->isLocked());
180 }
181 
182 /* Test case where the device/file doesn't exist. */
183 TEST_F(EStoragedTest, FormatNoDeviceFail)
184 {
185     /* Delete the test file. */
186     EXPECT_EQ(0, unlink(testFileName));
187 
188     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
189                  ResourceNotFound);
190     EXPECT_FALSE(esObject->isLocked());
191 
192     /* Create the test file again, so that the TearDown function works. */
193     testFile.open(testFileName,
194                   std::ios::out | std::ios::binary | std::ios::trunc);
195     testFile.close();
196 }
197 
198 /* Test case where we fail to format the LUKS device. */
199 TEST_F(EStoragedTest, FormatFail)
200 {
201     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _))
202         .WillOnce(Return(-1));
203 
204     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
205                  InternalFailure);
206     EXPECT_FALSE(esObject->isLocked());
207 }
208 
209 /* Test case where we fail to set the password for the LUKS device. */
210 TEST_F(EStoragedTest, AddKeyslotFail)
211 {
212     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
213 
214     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
215         .WillOnce(Return(-1));
216 
217     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
218                  InternalFailure);
219     EXPECT_TRUE(esObject->isLocked());
220 }
221 
222 /* Test case where we fail to load the LUKS header. */
223 TEST_F(EStoragedTest, LoadLuksHeaderFail)
224 {
225     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
226 
227     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
228         .Times(1);
229 
230     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).WillOnce(Return(-1));
231 
232     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
233                  InternalFailure);
234     EXPECT_TRUE(esObject->isLocked());
235 }
236 
237 /* Test case where we fail to activate the LUKS device. */
238 TEST_F(EStoragedTest, ActivateFail)
239 {
240     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
241 
242     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
243         .Times(1);
244 
245     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
246 
247     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
248         .WillOnce(Return(-1));
249 
250     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
251                  InternalFailure);
252     EXPECT_TRUE(esObject->isLocked());
253 }
254 
255 /* Test case where we fail to create the filesystem. */
256 TEST_F(EStoragedTest, CreateFilesystemFail)
257 {
258     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
259 
260     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
261         .Times(1);
262 
263     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
264 
265     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
266         .Times(1);
267 
268     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(-1));
269 
270     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
271                  InternalFailure);
272     EXPECT_FALSE(esObject->isLocked());
273 }
274 
275 /* Test case where we fail to create the mount point. */
276 TEST_F(EStoragedTest, CreateMountPointFail)
277 {
278     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
279 
280     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
281         .Times(1);
282 
283     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
284 
285     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
286         .Times(1);
287 
288     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
289 
290     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
291         .WillOnce(Return(false));
292 
293     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
294         .WillOnce(Return(false));
295 
296     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
297                  InternalFailure);
298     EXPECT_FALSE(esObject->isLocked());
299 }
300 
301 /* Test case where we fail to mount the filesystem. */
302 TEST_F(EStoragedTest, MountFail)
303 {
304     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
305 
306     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
307         .Times(1);
308 
309     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
310 
311     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
312         .Times(1);
313 
314     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
315 
316     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
317         .WillOnce(Return(false));
318 
319     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
320         .WillOnce(Return(true));
321 
322     EXPECT_CALL(*mockFsIface,
323                 doMount(ContainsRegex("/dev/mapper/"),
324                         StrEq(esObject->getMountPoint()), _, _, _))
325         .WillOnce(Return(-1));
326 
327     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
328         .WillOnce(Return(true));
329 
330     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
331                  InternalFailure);
332     EXPECT_FALSE(esObject->isLocked());
333 }
334 
335 /* Test case where we fail to unmount the filesystem. */
336 TEST_F(EStoragedTest, UnmountFail)
337 {
338     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
339 
340     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
341         .Times(1);
342 
343     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
344 
345     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
346         .Times(1);
347 
348     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
349 
350     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
351         .WillOnce(Return(false));
352 
353     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
354         .WillOnce(Return(true));
355 
356     EXPECT_CALL(*mockFsIface,
357                 doMount(ContainsRegex("/dev/mapper/"),
358                         StrEq(esObject->getMountPoint()), _, _, _))
359         .WillOnce(Return(0));
360 
361     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
362         .WillOnce(Return(-1));
363 
364     esObject->formatLuks(password, Volume::FilesystemType::ext4);
365     EXPECT_FALSE(esObject->isLocked());
366 
367     EXPECT_THROW(esObject->lock(), InternalFailure);
368     EXPECT_FALSE(esObject->isLocked());
369 }
370 
371 /* Test case where we fail to remove the mount point. */
372 TEST_F(EStoragedTest, RemoveMountPointFail)
373 {
374     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
375 
376     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
377         .Times(1);
378 
379     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
380 
381     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
382         .Times(1);
383 
384     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
385 
386     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
387         .WillOnce(Return(false));
388 
389     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
390         .WillOnce(Return(true));
391 
392     EXPECT_CALL(*mockFsIface,
393                 doMount(ContainsRegex("/dev/mapper/"),
394                         StrEq(esObject->getMountPoint()), _, _, _))
395         .WillOnce(Return(0));
396 
397     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
398         .WillOnce(Return(0));
399 
400     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
401         .WillOnce(Return(false));
402 
403     esObject->formatLuks(password, Volume::FilesystemType::ext4);
404     EXPECT_FALSE(esObject->isLocked());
405 
406     /* This will fail to remove the mount point. */
407     EXPECT_THROW(esObject->lock(), InternalFailure);
408     EXPECT_FALSE(esObject->isLocked());
409 }
410 
411 /* Test case where we fail to deactivate the LUKS device. */
412 TEST_F(EStoragedTest, DeactivateFail)
413 {
414     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
415 
416     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
417         .Times(1);
418 
419     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
420 
421     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
422         .Times(1);
423 
424     EXPECT_CALL(*mockFsIface, runMkfs(testLuksDevName)).WillOnce(Return(0));
425 
426     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
427         .WillOnce(Return(false));
428 
429     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
430         .WillOnce(Return(true));
431 
432     EXPECT_CALL(*mockFsIface,
433                 doMount(ContainsRegex("/dev/mapper/"),
434                         StrEq(esObject->getMountPoint()), _, _, _))
435         .WillOnce(Return(0));
436 
437     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
438         .WillOnce(Return(0));
439 
440     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
441         .WillOnce(Return(true));
442 
443     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _)).WillOnce(Return(-1));
444 
445     /* Format the encrypted device. */
446     esObject->formatLuks(password, Volume::FilesystemType::ext4);
447     EXPECT_FALSE(esObject->isLocked());
448 
449     EXPECT_THROW(esObject->lock(), InternalFailure);
450     EXPECT_FALSE(esObject->isLocked());
451 }
452 
453 } // namespace estoraged_test
454