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