1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include <nlohmann/json.hpp>
18 #include <platform/dirutils.h>
19 #include <cstdlib>
20 #include <gsl/gsl>
21 #include <iostream>
22 #include <map>
23 
24 #include <cerrno>
25 #include <cstring>
26 #include "auditconfig.h"
27 #include "mock_auditconfig.h"
28 
29 #include <folly/portability/GTest.h>
30 
31 class AuditConfigTest : public ::testing::Test {
32 protected:
SetUpTestCase()33     static void SetUpTestCase() {
34         testdir = cb::io::mkdtemp("auditconfig-test-");
35         // Create the audit_events.json file needed by the configuration
36         std::string fname = testdir + std::string("/audit_events.json");
37         FILE* fd = fopen(fname.c_str(), "w");
38         ASSERT_NE(nullptr, fd)
39             << "Unable to open test file '" << fname << "' error: "
40             << strerror(errno);
41         ASSERT_EQ(0, fclose(fd))
42             << "Failed to close test file '" << fname << "' error: "
43             << strerror(errno);
44     }
45 
TearDownTestCase()46     static void TearDownTestCase() {
47         cb::io::rmrf(testdir);
48     }
49 
50     nlohmann::json json;
51     AuditConfig config;
52     static std::string testdir;
53 
SetUp()54     virtual void SetUp() {
55         json = createDefaultConfig();
56     }
57 
TearDown()58     virtual void TearDown() {
59     }
60 
createDefaultConfig()61     nlohmann::json createDefaultConfig() {
62         nlohmann::json root;
63         root["version"] = 2;
64         root["rotate_size"] = 20 * 1024 * 1024;
65         root["rotate_interval"] = 900;
66         root["auditd_enabled"] = true;
67         root["buffered"] = true;
68         root["log_path"] = testdir;
69         root["descriptors_path"] = testdir;
70         nlohmann::json sync = nlohmann::json::array();
71         root["sync"] = sync;
72         nlohmann::json disabled = nlohmann::json::array();
73         root["disabled"] = disabled;
74         nlohmann::json event_states;
75         root["event_states"] = event_states;
76         nlohmann::json disabled_userids = nlohmann::json::array();
77         root["disabled_userids"] = disabled_userids;
78         root["filtering_enabled"] = true;
79         root["uuid"] = "123456";
80 
81         return root;
82     }
83 };
84 
85 std::string AuditConfigTest::testdir;
86 
TEST_F(AuditConfigTest, UnknownTag)87 TEST_F(AuditConfigTest, UnknownTag) {
88     json["foo"] = 5;
89     EXPECT_THROW(config.initialize_config(json), std::string);
90 }
91 
92 // version
93 
TEST_F(AuditConfigTest, TestGetVersion)94 TEST_F(AuditConfigTest, TestGetVersion) {
95     ASSERT_NO_THROW(config.initialize_config(json));
96     EXPECT_EQ(2, config.get_version());
97 }
98 
TEST_F(AuditConfigTest, TestNoVersion)99 TEST_F(AuditConfigTest, TestNoVersion) {
100     json.erase("version");
101     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
102 }
103 
TEST_F(AuditConfigTest, TestIllegalDatatypeVersion)104 TEST_F(AuditConfigTest, TestIllegalDatatypeVersion) {
105     json["version"] = "foobar";
106     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
107 }
108 
TEST_F(AuditConfigTest, TestLegalVersion)109 TEST_F(AuditConfigTest, TestLegalVersion) {
110     for (int version = -100; version < 100; ++version) {
111         json["version"] = version;
112         if (version == 1) {
113             json.erase("filtering_enabled");
114             json.erase("disabled_userids");
115             json.erase("event_states");
116             json.erase("uuid");
117         }
118         if (version == 2) {
119             json["filtering_enabled"] = true;
120             nlohmann::json disabled_userids = nlohmann::json::array();
121             json["disabled_userids"] = disabled_userids;
122             nlohmann::json event_states;
123             json["event_states"] = event_states;
124             json["uuid"] = "123456";
125         }
126         if ((version == 1) || (version == 2)) {
127             EXPECT_NO_THROW(config.initialize_config(json));
128         } else {
129             EXPECT_THROW(config.initialize_config(json), std::string);
130         }
131     }
132 }
133 
134 // rotate_size
135 
TEST_F(AuditConfigTest, TestNoRotateSize)136 TEST_F(AuditConfigTest, TestNoRotateSize) {
137     json.erase("rotate_size");
138     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
139 }
140 
TEST_F(AuditConfigTest, TestRotateSizeSetGet)141 TEST_F(AuditConfigTest, TestRotateSizeSetGet) {
142     for (size_t ii = 0; ii < 100; ++ii) {
143         config.set_rotate_size(ii);
144         EXPECT_EQ(ii, config.get_rotate_size());
145     }
146 }
147 
TEST_F(AuditConfigTest, TestRotateSizeIllegalDatatype)148 TEST_F(AuditConfigTest, TestRotateSizeIllegalDatatype) {
149     json["rotate_size"] = "foobar";
150     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
151 }
152 
TEST_F(AuditConfigTest, TestRotateSizeLegalValue)153 TEST_F(AuditConfigTest, TestRotateSizeLegalValue) {
154     json["rotate_size"] = 100;
155     EXPECT_NO_THROW(config.initialize_config(json));
156 }
157 
TEST_F(AuditConfigTest, TestRotateSizeIllegalValue)158 TEST_F(AuditConfigTest, TestRotateSizeIllegalValue) {
159     json["rotate_size"] = -1;
160     EXPECT_THROW(config.initialize_config(json), std::string);
161 }
162 
163 // rotate_interval
164 
TEST_F(AuditConfigTest, TestNoRotateInterval)165 TEST_F(AuditConfigTest, TestNoRotateInterval) {
166     json.erase("rotate_interval");
167     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
168 }
169 
TEST_F(AuditConfigTest, TestRotateIntervalSetGet)170 TEST_F(AuditConfigTest, TestRotateIntervalSetGet) {
171     AuditConfig defaultvalue;
172     const uint32_t min_file_rotation_time = defaultvalue.get_min_file_rotation_time();
173     for (size_t ii = min_file_rotation_time;
174          ii < min_file_rotation_time + 10;
175          ++ii) {
176         config.set_rotate_interval(uint32_t(ii));
177         EXPECT_EQ(ii, config.get_rotate_interval());
178     }
179 }
180 
TEST_F(AuditConfigTest, TestRotateIntervalIllegalDatatype)181 TEST_F(AuditConfigTest, TestRotateIntervalIllegalDatatype) {
182     json["rotate_interval"] = "foobar";
183     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
184 }
185 
TEST_F(AuditConfigTest, TestRotateIntervalLegalValue)186 TEST_F(AuditConfigTest, TestRotateIntervalLegalValue) {
187     AuditConfig defaultvalue;
188     const uint32_t min_file_rotation_time = defaultvalue.get_min_file_rotation_time();
189     const uint32_t max_file_rotation_time = defaultvalue.get_max_file_rotation_time();
190 
191     for (uint32_t ii = min_file_rotation_time; ii < max_file_rotation_time;
192          ii += 1000) {
193         json["rotate_interval"] = ii;
194         EXPECT_NO_THROW(config.initialize_config(json));
195     }
196 }
197 
TEST_F(AuditConfigTest, TestRotateIntervalIllegalValue)198 TEST_F(AuditConfigTest, TestRotateIntervalIllegalValue) {
199     AuditConfig defaultvalue;
200     const uint32_t min_file_rotation_time = defaultvalue.get_min_file_rotation_time();
201     const uint32_t max_file_rotation_time = defaultvalue.get_max_file_rotation_time();
202 
203     json["rotate_interval"] = min_file_rotation_time - 1;
204     EXPECT_THROW(config.initialize_config(json), std::string);
205     json["rotate_interval"] = max_file_rotation_time + 1;
206     EXPECT_THROW(config.initialize_config(json), std::string);
207 }
208 
209 // auditd_enabled
210 
TEST_F(AuditConfigTest, TestNoAuditdEnabled)211 TEST_F(AuditConfigTest, TestNoAuditdEnabled) {
212     json.erase("auditd_enabled");
213     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
214 }
215 
TEST_F(AuditConfigTest, TestGetSetAuditdEnabled)216 TEST_F(AuditConfigTest, TestGetSetAuditdEnabled) {
217     config.set_auditd_enabled(true);
218     EXPECT_TRUE(config.is_auditd_enabled());
219     config.set_auditd_enabled(false);
220     EXPECT_FALSE(config.is_auditd_enabled());
221 }
222 
TEST_F(AuditConfigTest, TestIllegalDatatypeAuditdEnabled)223 TEST_F(AuditConfigTest, TestIllegalDatatypeAuditdEnabled) {
224     json["auditd_enabled"] = "foobar";
225     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
226 }
227 
TEST_F(AuditConfigTest, TestLegalAuditdEnabled)228 TEST_F(AuditConfigTest, TestLegalAuditdEnabled) {
229     json["auditd_enabled"] = true;
230     EXPECT_NO_THROW(config.initialize_config(json));
231 
232     json["auditd_enabled"] = false;
233     EXPECT_NO_THROW(config.initialize_config(json));
234 }
235 
236 // buffered
237 
TEST_F(AuditConfigTest, TestNoBuffered)238 TEST_F(AuditConfigTest, TestNoBuffered) {
239     // buffered is optional, and enabled unless explicitly disabled
240     json.erase("buffered");
241     EXPECT_NO_THROW(config.initialize_config(json));
242     EXPECT_TRUE(config.is_buffered());
243 }
244 
TEST_F(AuditConfigTest, TestGetSetBuffered)245 TEST_F(AuditConfigTest, TestGetSetBuffered) {
246     config.set_buffered(true);
247     EXPECT_TRUE(config.is_buffered());
248     config.set_buffered(false);
249     EXPECT_FALSE(config.is_buffered());
250 }
251 
TEST_F(AuditConfigTest, TestIllegalDatatypeBuffered)252 TEST_F(AuditConfigTest, TestIllegalDatatypeBuffered) {
253     json["buffered"] = "foobar";
254     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
255 }
256 
TEST_F(AuditConfigTest, TestLegalBuffered)257 TEST_F(AuditConfigTest, TestLegalBuffered) {
258     json["buffered"] = true;
259     EXPECT_NO_THROW(config.initialize_config(json));
260 
261     json["buffered"] = false;
262     EXPECT_NO_THROW(config.initialize_config(json));
263 }
264 
265 // log_path
266 
TEST_F(AuditConfigTest, TestNoLogPath)267 TEST_F(AuditConfigTest, TestNoLogPath) {
268     json.erase("log_path");
269     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
270 }
271 
TEST_F(AuditConfigTest, TestGetSetLogPath)272 TEST_F(AuditConfigTest, TestGetSetLogPath) {
273     EXPECT_NO_THROW(config.set_log_directory(testdir));
274     EXPECT_EQ(testdir, config.get_log_directory());
275 }
276 
TEST_F(AuditConfigTest, TestGetSetSanitizeLogPath)277 TEST_F(AuditConfigTest, TestGetSetSanitizeLogPath) {
278     // Trim of trailing paths
279     std::string path = testdir + std::string("/");
280     EXPECT_NO_THROW(config.set_log_directory(path));
281     EXPECT_EQ(testdir, config.get_log_directory());
282 }
283 
284 #ifdef WIN32
TEST_F(AuditConfigTest, TestGetSetSanitizeLogPathMixedSeparators)285 TEST_F(AuditConfigTest, TestGetSetSanitizeLogPathMixedSeparators) {
286     // Trim of trailing paths
287     std::string path = testdir + std::string("/mydir\\baddir");
288     EXPECT_NO_THROW(config.set_log_directory(path));
289     EXPECT_EQ(testdir + "\\mydir\\baddir", config.get_log_directory());
290     EXPECT_NO_THROW(cb::io::rmrf(config.get_log_directory()))
291         << "Failed to remove: " << config.get_log_directory()
292         << ": " << strerror(errno) << std::endl;
293 }
294 #endif
295 
296 #ifndef WIN32
TEST_F(AuditConfigTest, TestFailToCreateDirLogPath)297 TEST_F(AuditConfigTest, TestFailToCreateDirLogPath) {
298     json["log_path"] = "/itwouldsuckifthisexists";
299     EXPECT_THROW(config.initialize_config(json), std::string);
300 }
301 #endif
302 
TEST_F(AuditConfigTest, TestCreateDirLogPath)303 TEST_F(AuditConfigTest, TestCreateDirLogPath) {
304     std::string path = testdir + std::string("/mybar");
305     json["log_path"] = path;
306     EXPECT_NO_THROW(config.initialize_config(json));
307     EXPECT_NO_THROW(cb::io::rmrf(config.get_log_directory()))
308         << "Failed to remove: " << config.get_log_directory()
309         << ": " << strerror(errno) << std::endl;
310 }
311 
312 // descriptors_path
313 
TEST_F(AuditConfigTest, TestNoDescriptorsPath)314 TEST_F(AuditConfigTest, TestNoDescriptorsPath) {
315     json.erase("descriptors_path");
316     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
317 }
318 
TEST_F(AuditConfigTest, TestGetSetDescriptorsPath)319 TEST_F(AuditConfigTest, TestGetSetDescriptorsPath) {
320     EXPECT_NO_THROW(config.set_descriptors_path(testdir));
321     EXPECT_EQ(testdir, config.get_descriptors_path());
322 }
323 
TEST_F(AuditConfigTest, TestSetMissingEventDescrFileDescriptorsPath)324 TEST_F(AuditConfigTest, TestSetMissingEventDescrFileDescriptorsPath) {
325     std::string path = testdir + std::string("/foo");
326     cb::io::mkdirp(path);
327 
328     EXPECT_THROW(config.set_descriptors_path(path), std::string);
329     EXPECT_NO_THROW(cb::io::rmrf(path))
330         << "Failed to remove: " << path
331         << ": " << strerror(errno) << std::endl;
332 }
333 
334 // Sync
335 
TEST_F(AuditConfigTest, TestNoSync)336 TEST_F(AuditConfigTest, TestNoSync) {
337     json.erase("sync");
338     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
339 }
340 
TEST_F(AuditConfigTest, TestSpecifySync)341 TEST_F(AuditConfigTest, TestSpecifySync) {
342     nlohmann::json array = nlohmann::json::array();
343     for (int ii = 0; ii < 10; ++ii) {
344         array.push_back(ii);
345     }
346     json["sync"] = array;
347     EXPECT_NO_THROW(config.initialize_config(json));
348 
349     for (uint32_t ii = 0; ii < 100; ++ii) {
350         if (ii < 10) {
351             EXPECT_TRUE(config.is_event_sync(ii));
352         } else {
353             EXPECT_FALSE(config.is_event_sync(ii));
354         }
355     }
356 }
357 
358 // Disabled
359 
TEST_F(AuditConfigTest, TestNoDisabled)360 TEST_F(AuditConfigTest, TestNoDisabled) {
361     json.erase("disabled");
362     if (config.get_version() == 1) {
363         EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
364     } else {
365         EXPECT_NO_THROW(config.initialize_config(json));
366     }
367 }
368 
TEST_F(AuditConfigTest, TestSpecifyDisabled)369 TEST_F(AuditConfigTest, TestSpecifyDisabled) {
370     nlohmann::json array = nlohmann::json::array();
371     for (int ii = 0; ii < 10; ++ii) {
372         array.push_back(ii);
373     }
374     json["disabled"] = array;
375     EXPECT_NO_THROW(config.initialize_config(json));
376 
377     for (uint32_t ii = 0; ii < 100; ++ii) {
378         if (ii < 10 && config.get_version() == 1) {
379             EXPECT_TRUE(config.is_event_disabled(ii));
380         } else {
381             EXPECT_FALSE(config.is_event_disabled(ii));
382         }
383     }
384 }
385 
TEST_F(AuditConfigTest, TestSpecifyDisabledUsers)386 TEST_F(AuditConfigTest, TestSpecifyDisabledUsers) {
387     nlohmann::json array = nlohmann::json::array();
388     for (uint16_t ii = 0; ii < 10; ++ii) {
389         nlohmann::json userIdRoot;
390 
391         // In version 2 of the configuration we support domain or support
392         // however domain is the preferred notation.
393         // Have 10 users so make half use domain and half use source
394         if (ii < 5) {
395             userIdRoot["domain"] = "internal";
396         } else {
397             userIdRoot["source"] = "internal";
398         }
399         auto user = "user" + std::to_string(ii);
400         userIdRoot["user"] = user;
401         array.push_back(userIdRoot);
402     }
403     json["disabled_userids"] = array;
404     EXPECT_NO_THROW(config.initialize_config(json));
405 
406     for (uint16_t ii = 0; ii < 100; ++ii) {
407         const auto& domain = "internal";
408         const auto& user = "user" + std::to_string(ii);
409         const auto& userid = std::make_pair(domain, user);
410         if (ii < 10) {
411             EXPECT_TRUE(config.is_event_filtered(userid));
412         } else {
413             EXPECT_FALSE(config.is_event_filtered(userid));
414         }
415     }
416 }
417 
418 /**
419  * Tests that when converting a config containing a single disabled event it
420  * translates to a single entry in the json "disabled" array and the json
421  * "disabled_userids" array remains empty.
422  */
TEST_F(AuditConfigTest, AuditConfigDisabled)423 TEST_F(AuditConfigTest, AuditConfigDisabled) {
424     MockAuditConfig config;
425     nlohmann::json disabled = nlohmann::json::array();
426     disabled.push_back(1234);
427     config.public_set_disabled(disabled);
428 
429     auto json = config.to_json();
430     auto disabledArray = json["disabled"];
431 
432     EXPECT_EQ(1, disabledArray.size());
433     auto disabledUseridsArray = json["disabled_userids"];
434     EXPECT_EQ(0, disabledUseridsArray.size());
435 }
436 
437 /**
438  * Tests that when converting a config containing a single disabled_user it
439  * translates to a single entry in the json "disabled_userids" array and the
440  * json "disabled" array remains empty.
441  */
TEST_F(AuditConfigTest, AuditConfigDisabledUsers)442 TEST_F(AuditConfigTest, AuditConfigDisabledUsers) {
443     MockAuditConfig config;
444 
445     nlohmann::json disabledUserids = nlohmann::json::array();
446     nlohmann::json userIdRoot;
447     userIdRoot["domain"] = "internal";
448     userIdRoot["user"] = "johndoe";
449     disabledUserids.push_back(userIdRoot);
450     config.public_set_disabled_userids(disabledUserids);
451 
452     auto json = config.to_json();
453     auto disabledUseridsArray = json["disabled_userids"];
454     EXPECT_EQ(1, disabledUseridsArray.size());
455     auto disabledArray = json["disabled"];
456     EXPECT_EQ(0, disabledArray.size());
457 }
458 
459 // Test the filtering_enabled parameter
460 
TEST_F(AuditConfigTest, TestNoFilteringEnabled)461 TEST_F(AuditConfigTest, TestNoFilteringEnabled) {
462     json.erase("filtering_enabled");
463     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
464 }
465 
TEST_F(AuditConfigTest, TestGetSetFilteringEnabled)466 TEST_F(AuditConfigTest, TestGetSetFilteringEnabled) {
467     config.set_filtering_enabled(true);
468     EXPECT_TRUE(config.is_filtering_enabled());
469     config.set_filtering_enabled(false);
470     EXPECT_FALSE(config.is_filtering_enabled());
471 }
472 
TEST_F(AuditConfigTest, TestIllegalDatatypeFilteringEnabled)473 TEST_F(AuditConfigTest, TestIllegalDatatypeFilteringEnabled) {
474     json["filtering_enabled"] = "foobar";
475     EXPECT_THROW(config.initialize_config(json), nlohmann::json::exception);
476 }
477 
TEST_F(AuditConfigTest, TestLegalFilteringEnabled)478 TEST_F(AuditConfigTest, TestLegalFilteringEnabled) {
479     json["filtering_enabled"] = true;
480     EXPECT_NO_THROW(config.initialize_config(json));
481 
482     json["filtering_enabled"] = false;
483     EXPECT_NO_THROW(config.initialize_config(json));
484 }
485 
486 // The event_states list is optional and therefore if it does not exist
487 // it should not throw an exception.
TEST_F(AuditConfigTest, TestNoEventStates)488 TEST_F(AuditConfigTest, TestNoEventStates) {
489     json.erase("event_states");
490     EXPECT_NO_THROW(config.initialize_config(json));
491 }
492 
493 // Test that with an event_states object consisting of "enabled" and "disabled"
494 // the states get converted into corresponding EventStates.  Also if an event
495 // is not in the event_states object it has an EventState of undefined.
TEST_F(AuditConfigTest, TestSpecifyEventStates)496 TEST_F(AuditConfigTest, TestSpecifyEventStates) {
497     nlohmann::json object;
498     for (int ii = 0; ii < 5; ++ii) {
499         auto event = std::to_string(ii);
500         object[event] = "enabled";
501     }
502     for (int ii = 5; ii < 10; ++ii) {
503         auto event = std::to_string(ii);
504         object[event] = "disabled";
505     }
506     json["event_states"] = object;
507     EXPECT_NO_THROW(config.initialize_config(json));
508 
509     for (uint32_t ii = 0; ii < 20; ++ii) {
510         if (ii < 5) {
511             EXPECT_EQ(AuditConfig::EventState::enabled,
512                       config.get_event_state(ii));
513         } else if (ii < 10) {
514             EXPECT_EQ(AuditConfig::EventState::disabled,
515                       config.get_event_state(ii));
516         } else {
517             EXPECT_EQ(AuditConfig::EventState::undefined,
518                       config.get_event_state(ii));
519         }
520     }
521 }
522