2 * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @file test_cases_db.cpp
19 * @author Pawel Wieczorek <p.wieczorek2@samsung.com>
21 * @brief Tests for Cynara's mechanism assuring integrity of database
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>
31 #include <service_manager.h>
43 using namespace CynaraTestAdmin;
44 using namespace CynaraTestClient;
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('/');
56 void createDbFile(const std::string &filename)
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);
62 int ret = smack_fsetlabel(fileFd, CynaraTestConsts::LABEL.c_str(), SMACK_LABEL_ACCESS);
63 RUNNER_ASSERT_MSG(ret == 0, "Setting smack label failed");
66 void deleteDbFile(const std::string &filename)
68 RUNNER_ASSERT_ERRNO_MSG(!unlink(filename.c_str()), "Unable to unlink " << filename << " file");
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);
75 RUNNER_ASSERT_MSG(patternFile.is_open(), "Failed to open " << patternFile << ".");
76 RUNNER_ASSERT_MSG(resultFile.is_open(), "Failed to open " << resultFile << ".");
78 auto patternRecords = std::multiset<std::string>(std::istream_iterator<std::string>(patternFile),
79 std::istream_iterator<std::string>());
81 auto resultRecords = std::multiset<std::string>(std::istream_iterator<std::string>(resultFile),
82 std::istream_iterator<std::string>());
84 return patternRecords == resultRecords;
87 size_t glob_count(const std::string &source, const std::string &wildcard) {
88 //for counting files in directory
90 std::string pattern = source + wildcard;
92 //for freeing allocated memory
93 GlobPtr globbufPtr(&globbuf);
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 << ".");
99 return globbuf.gl_pathc;
102 size_t db_files_count(const std::string &source) {
103 size_t dbFilesCount = 0;
105 //database directory must not be empty
106 RUNNER_ASSERT_MSG(0 != (dbFilesCount = glob_count(source, directoryWildcard)),
107 "Unexpected condition: " << source << " was empty.");
112 const std::set<std::string> dump_glob_filenames(const glob_t &globbuf) {
113 std::set<std::string> set;
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));
123 const std::set<std::string> glob_filenames(const std::string &source, const std::string &wildcard) {
124 //for finding files matching pattern in directory
126 std::string pattern = source + wildcard;
128 //for freeing allocated memory
129 GlobPtr globbufPtr(&globbuf);
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 << ".");
135 return dump_glob_filenames(globbuf);
138 const std::set<std::string> db_files_pathnames(const std::string &source) {
139 return glob_filenames(source, directoryWildcard);
142 std::ostream& operator<<(std::ostream& os, const std::set<std::string> &set)
145 for (const auto &item : set) {
152 void compareDbs(const std::string &source)
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;
160 size_t patternFileCount = db_files_count(patternDir);
161 size_t resultFileCount = db_files_count(resultDir);
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));
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);
175 while ((direntPtr = readdir(patternDirPtr)) != nullptr) {
176 if (!strcmp(direntPtr->d_name, ".")
177 || !strcmp(direntPtr->d_name, ".."))
179 std::string patternName = patternDir + directorySeparator + direntPtr->d_name;
180 std::string resultName = CynaraTestConsts::DB_DIR + directorySeparator + direntPtr->d_name;
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);
188 } // anonymous namespace
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
199 void tcdb01_lockdown_init_failure_func()
204 const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
205 const char *extra = nullptr;
207 const auto fakeBackupGuard = CynaraTestConsts::DB_DIR + directorySeparator + "guard";
209 createDbFile(fakeBackupGuard);
210 admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra, CYNARA_API_OPERATION_FAILED);
212 deleteDbFile(fakeBackupGuard);
213 admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra);
215 restartCynaraServiceAndSockets();
216 compareDbs(defDbAllow);
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
228 void tcdb02_write_to_backup_failure_func()
233 const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
234 const char *extra = nullptr;
236 const auto fakeBucketDumpFile = CynaraTestConsts::DB_DIR + directorySeparator + "_~";
238 admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra);
239 compareDbs(defDbAllow);
241 createDbFile(fakeBucketDumpFile);
242 admin.setBucket(bucket, CYNARA_ADMIN_DENY, extra, CYNARA_API_OPERATION_FAILED);
244 restartCynaraServiceAndSockets();
245 compareDbs(defDbAllow);
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
258 void tcdb03_invalid_and_valid_backup_removal_func()
263 const char *bucket = CYNARA_ADMIN_DEFAULT_BUCKET;
264 const char *extra = nullptr;
266 const auto defaultBucketDumpFile = CynaraTestConsts::DB_DIR + directorySeparator + "_~";
268 createDbFile(defaultBucketDumpFile);
269 admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra, CYNARA_API_OPERATION_FAILED);
271 restartCynaraServiceAndSockets();
274 admin.setBucket(bucket, CYNARA_ADMIN_ALLOW, extra);
276 restartCynaraServiceAndSockets();
277 compareDbs(defDbAllow);
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
286 void tcdb04_dumped_file_binary_comparison_func()
290 ServiceManager serviceManager(CynaraTestConsts::SERVICE);
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;
299 CynaraPoliciesContainer cp;
300 cp.add(bucket, client, user, privilege, CYNARA_ADMIN_DENY, extra);
301 admin.setPolicies(cp, CYNARA_API_SUCCESS);
304 compareDbs(nonEmptyDb);
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
315 * 3. Check if any of mentioned above files still remained
317 void tcdb05_non_indexed_files_removal_func()
319 std::vector<std::string> filenames = { "_broken-backup~", "_non-indexed-bucket",
320 "some-file-that-doesnt-belong-here" };
322 for (const auto &filename : filenames) {
323 auto garbageFilename = CynaraTestConsts::DB_DIR + directorySeparator + filename;
324 createDbFile(garbageFilename);
327 restartCynaraServiceAndSockets();
331 RUNNER_TEST_GROUP_INIT(cynara_db_tests)
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)