a0f4d78d71715c019565f31e6ca95c1ac254b51c
[platform/core/test/security-tests.git] / src / cynara-tests / test_cases_db.cpp
1 /*
2  * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 /**
18  * @file        test_cases_db.cpp
19  * @author      Pawel Wieczorek <p.wieczorek2@samsung.com>
20  * @version     0.1
21  * @brief       Tests for Cynara's mechanism assuring integrity of database
22  */
23
24 #include <cynara_test_admin.h>
25 #include <cynara_test_client.h>
26 #include <cynara_test_commons.h>
27 #include <cynara_test_env.h>
28 #include <dpl/test/test_runner.h>
29 #include <sys/smack.h>
30
31 #include <service_manager.h>
32 #include <dirent.h>
33 #include <fcntl.h>
34 #include <fstream>
35 #include <glob.h>
36 #include <iterator>
37 #include <memory.h>
38 #include <set>
39 #include <string>
40 #include <unistd.h>
41 #include <vector>
42
43 using namespace CynaraTestAdmin;
44 using namespace CynaraTestClient;
45
46 namespace
47 {
48
49 const std::string defDb("default");
50 const std::string defDbAllow("defaultAllowed");
51 const std::string nonEmptyDb("nonEmptyDatabase");
52 const std::string cynaraTestPatternsPath("/etc/security-tests/db_patterns/");
53 const std::string directoryWildcard("/*");
54 const char directorySeparator('/');
55
56 void createDbFile(const std::string &filename)
57 {
58     int fileFd = TEMP_FAILURE_RETRY(creat(filename.c_str(), 0000));
59     RUNNER_ASSERT_ERRNO_MSG(fileFd > 0, "Creating " << filename << " file failed");
60     FdUniquePtr fileFdPtr(&fileFd);
61
62     int ret = smack_fsetlabel(fileFd, CynaraTestConsts::LABEL.c_str(), SMACK_LABEL_ACCESS);
63     RUNNER_ASSERT_MSG(ret == 0, "Setting smack label failed");
64 }
65
66 void deleteDbFile(const std::string &filename)
67 {
68     RUNNER_ASSERT_ERRNO_MSG(!unlink(filename.c_str()), "Unable to unlink " << filename << " file");
69 }
70
71 bool unordered_files_match(const std::string &patternFilePath, const std::string &resultFilePath) {
72     std::ifstream patternFile(patternFilePath, std::ifstream::in | std::ifstream::binary);
73     std::ifstream resultFile(resultFilePath, std::ifstream::in | std::ifstream::binary);
74
75     RUNNER_ASSERT_MSG(patternFile.is_open(), "Failed to open " << patternFile << ".");
76     RUNNER_ASSERT_MSG(resultFile.is_open(), "Failed to open " << resultFile << ".");
77
78     auto patternRecords = std::multiset<std::string>(std::istream_iterator<std::string>(patternFile),
79                                                      std::istream_iterator<std::string>());
80
81     auto resultRecords = std::multiset<std::string>(std::istream_iterator<std::string>(resultFile),
82                                                     std::istream_iterator<std::string>());
83
84     return patternRecords == resultRecords;
85 }
86
87 size_t glob_count(const std::string &source, const std::string &wildcard) {
88     //for counting files in directory
89     glob_t globbuf;
90     std::string pattern = source + wildcard;
91
92     //for freeing allocated memory
93     GlobPtr globbufPtr(&globbuf);
94
95     //actually count files in directory - including dotfiles
96     RUNNER_ASSERT_MSG(0 == glob(pattern.c_str(), GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf),
97                       "Failed to search for requested pathnames in " << source << ".");
98
99     return globbuf.gl_pathc;
100 }
101
102 size_t db_files_count(const std::string &source) {
103     size_t dbFilesCount = 0;
104
105     //database directory must not be empty
106     RUNNER_ASSERT_MSG(0 != (dbFilesCount = glob_count(source, directoryWildcard)),
107                       "Unexpected condition: " << source << " was empty.");
108
109     return dbFilesCount;
110 }
111
112 const std::set<std::string> dump_glob_filenames(const glob_t &globbuf) {
113     std::set<std::string> set;
114
115     for (unsigned i = 0; i < globbuf.gl_pathc; ++i) {
116         std::string filename(globbuf.gl_pathv[i]);
117         set.insert(filename.substr(filename.find_last_of(directorySeparator)+1));
118     }
119
120     return set;
121 }
122
123 const std::set<std::string> glob_filenames(const std::string &source, const std::string &wildcard) {
124     //for finding files matching pattern in directory
125     glob_t globbuf;
126     std::string pattern = source + wildcard;
127
128     //for freeing allocated memory
129     GlobPtr globbufPtr(&globbuf);
130
131     //actually find files matching pattern in directory - including dotfiles
132     RUNNER_ASSERT_MSG(0 == glob(pattern.c_str(), GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf),
133                       "Failed to search for requested pathnames in " << source << ".");
134
135     return dump_glob_filenames(globbuf);
136 }
137
138 const std::set<std::string> db_files_pathnames(const std::string &source) {
139     return glob_filenames(source, directoryWildcard);
140 }
141
142 std::ostream& operator<<(std::ostream& os, const std::set<std::string> &set)
143 {
144     os << "{";
145     for (const auto &item : set) {
146         os << " " << item;
147     }
148     os << " }";
149     return os;
150 }
151
152 void compareDbs(const std::string &source)
153 {
154     //for accessing files in directory
155     std::string patternDir = cynaraTestPatternsPath + source;
156     std::string resultDir = CynaraTestConsts::DB_DIR;
157     DIR *patternDirPtr = nullptr;
158     struct dirent *direntPtr;
159
160     size_t patternFileCount = db_files_count(patternDir);
161     size_t resultFileCount = db_files_count(resultDir);
162
163     //directories do not match if there is different number of files
164     RUNNER_ASSERT_MSG(patternFileCount == resultFileCount,
165                       "No match in database and pattern directory file count: "
166                           << resultFileCount << " != " << patternFileCount << "." << std::endl
167                           << "Expected: " << db_files_pathnames(patternDir) << std::endl
168                           << "Actual:   " << db_files_pathnames(resultDir));
169
170     //compare files in database directory with pattern directory
171     RUNNER_ASSERT_ERRNO_MSG(patternDirPtr = opendir(patternDir.c_str()),
172                             "Opening " << patternDir << " directory failed");
173     DirPtr patternDirScopedPtr(patternDirPtr);
174
175     while ((direntPtr = readdir(patternDirPtr)) != nullptr) {
176         if (!strcmp(direntPtr->d_name, ".")
177          || !strcmp(direntPtr->d_name, ".."))
178             continue;
179         std::string patternName = patternDir + directorySeparator + direntPtr->d_name;
180         std::string resultName = CynaraTestConsts::DB_DIR + directorySeparator + direntPtr->d_name;
181
182         //comparing file saved db dir with reference file from patterns dir
183         RUNNER_ASSERT_MSG(true == unordered_files_match(patternName, resultName),
184                           "No match in stored file and pattern file: " << resultName);
185     }
186 }
187
188 } // anonymous namespace
189
190 /**
191  * @brief   Lockdown initialization failure caused by fake guard existence
192  * @test    Expected result: refuse to write data to storage as long as guard file creation fails
193  * 1. Create fake guard file with 0000 attributes in policy database
194  * 2. Try to make a change (ALLOW) in default bucket (data dump should fail)
195  * 3. Delete fake guard file from policy database
196  * 4. Retry to make a change (ALLOW) in default bucket (data dump should proceed)
197  * 5. Check if database is saved correctly
198  */
199 void tcdb01_lockdown_init_failure_func()
200 {
201     Admin admin;
202     Client cynara;
203
204     const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
205     const char *extra = nullptr;
206
207     const auto fakeBackupGuard = CynaraTestConsts::DB_DIR + directorySeparator + "guard";
208
209     createDbFile(fakeBackupGuard);
210     admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra, CYNARA_API_OPERATION_FAILED);
211
212     deleteDbFile(fakeBackupGuard);
213     admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra);
214
215     restartCynaraServiceAndSockets();
216     compareDbs(defDbAllow);
217 }
218
219 /**
220  * @brief   Failure during writing to backup (before lockdown)
221  * @test    Expected result: read from primary policy database
222  * 1. Write ALLOW to default bucket
223  * 2. Check if data is saved correctly
224  * 3. Create fake backup file with 0000 attributes in policy database
225  * 4. Try to make a change (DENY) in default bucket (data dump should fail)
226  * 5. Reload Cynara - policies loaded from default bucket should still be ALLOW
227  */
228 void tcdb02_write_to_backup_failure_func()
229 {
230     Admin admin;
231     Client cynara;
232
233     const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
234     const char *extra = nullptr;
235
236     const auto fakeBucketDumpFile = CynaraTestConsts::DB_DIR + directorySeparator + "_~";
237
238     admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra);
239     compareDbs(defDbAllow);
240
241     createDbFile(fakeBucketDumpFile);
242     admin.setBucket(bucket, CYNARA_ADMIN_DENY, extra, CYNARA_API_OPERATION_FAILED);
243
244     restartCynaraServiceAndSockets();
245     compareDbs(defDbAllow);
246 }
247
248 /**
249  * @brief   Check whether both invalid and valid backup databases are removed
250  * @test    Expected result: no unnecessary backup files in policy database directory
251  * 1. Fail writing to backup database
252  * 2. Reload Cynara - policies should be loaded from primary (valid) database
253  * 3. Check if all backup files were removed
254  * 4. Successfully write changes to database
255  * 5. Reload Cynara - policies should be loaded from primary (revalidated) database
256  * 6. Check if all backup files were removed
257  */
258 void tcdb03_invalid_and_valid_backup_removal_func()
259 {
260     Admin admin;
261     Client cynara;
262
263     const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
264     const char *extra = nullptr;
265
266     const auto defaultBucketDumpFile = CynaraTestConsts::DB_DIR + directorySeparator + "_~";
267
268     createDbFile(defaultBucketDumpFile);
269     admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra, CYNARA_API_OPERATION_FAILED);
270
271     restartCynaraServiceAndSockets();
272     compareDbs(defDb);
273
274     admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra);
275
276     restartCynaraServiceAndSockets();
277     compareDbs(defDbAllow);
278 }
279
280 /**
281  * @brief   Comparison between database modified by Cynara with expected one
282  * @test    Expected result: no differences between those files
283  * 1. Write sample policy to database (and let it save to storage)
284  * 2. Compare freshly saved files with samples from test patterns directory
285  */
286 void tcdb04_dumped_file_binary_comparison_func()
287 {
288     Admin admin;
289     Client cynara;
290     ServiceManager serviceManager(CynaraTestConsts::SERVICE);
291
292     const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
293     const char *client = "client";
294     const char *user = "user";
295     const char *privilege = "privilege";
296     const char *extra = nullptr;
297
298     {
299         CynaraPoliciesContainer cp;
300         cp.add(bucket, client, user, privilege, CYNARA_ADMIN_DENY, extra);
301         admin.setPolicies(cp, CYNARA_API_SUCCESS);
302     }
303
304     compareDbs(nonEmptyDb);
305 }
306
307 /**
308  * @brief   Invalid database files removal
309  * @test    Expected result: no unnecessary files in policy database directory
310  * 1. Fill Cynara's policy database directory with garbage:
311  *         - Sample backup file which should be removed earlier
312  *         - Sample bucket file which is not mentioned in index (shouldn't exist at all)
313  *         - Sample files which don't belong to database
314  * 2. Reload Cynara
315  * 3. Check if any of mentioned above files still remained
316  */
317 void tcdb05_non_indexed_files_removal_func()
318 {
319     std::vector<std::string> filenames = { "_broken-backup~", "_non-indexed-bucket",
320                                            "some-file-that-doesnt-belong-here" };
321
322     for (const auto &filename : filenames) {
323         auto garbageFilename = CynaraTestConsts::DB_DIR + directorySeparator + filename;
324         createDbFile(garbageFilename);
325     }
326
327     restartCynaraServiceAndSockets();
328     compareDbs(defDb);
329 }
330
331 RUNNER_TEST_GROUP_INIT(cynara_db_tests)
332
333 RUN_CYNARA_TEST(tcdb01_lockdown_init_failure)
334 RUN_CYNARA_TEST(tcdb02_write_to_backup_failure)
335 RUN_CYNARA_TEST(tcdb03_invalid_and_valid_backup_removal)
336 RUN_CYNARA_TEST(tcdb04_dumped_file_binary_comparison)
337 RUN_CYNARA_TEST(tcdb05_non_indexed_files_removal)