xref: /6.0.3/kv_engine/auditd/tests/testauditd.cc (revision 319eccf9)
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 "config.h"
18 
19 #include <cJSON.h>
20 #include <getopt.h>
21 #include <gtest/gtest.h>
22 #include <memcached/audit_interface.h>
23 #include <memcached/isotime.h>
24 #include <platform/dirutils.h>
25 #include <chrono>
26 #include <condition_variable>
27 #include <cstddef>
28 #include <fstream>
29 #include <iostream>
30 #include <limits>
31 #include <mutex>
32 #include <sstream>
33 #include <thread>
34 
35 #include "auditd/src/audit.h"
36 #include "auditd/src/auditconfig.h"
37 #include "auditd/tests/mock_auditconfig.h"
38 #include "memcached/extension.h"
39 
40 // The event descriptor file is normally stored in the directory named
41 // auditd relative to the binary.. let's just use that as the default
42 // and allow the user to override it with -e
43 static std::string event_descriptor("auditd");
44 
45 static std::mutex mutex;
46 static std::condition_variable cond;
47 static bool ready = false;
48 
49 extern "C" {
notify_io_complete(gsl::not_null<const void * >,ENGINE_ERROR_CODE)50 static void notify_io_complete(gsl::not_null<const void*>, ENGINE_ERROR_CODE) {
51     std::lock_guard<std::mutex> lock(mutex);
52     ready = true;
53     cond.notify_one();
54 }
55 }
56 
57 class AuditDaemonTest
58     : public ::testing::TestWithParam<std::tuple<bool, bool>> {
59 public:
SetUpTestCase()60     static void SetUpTestCase() {
61         // create the test directory
62         testdir = std::string("auditd-test-") + std::to_string(cb_getpid());
63         try {
64             cb::io::rmrf(testdir);
65         } catch (std::system_error& e) {
66             if (e.code() != std::error_code(ENOENT, std::system_category())) {
67                 throw e;
68             }
69         }
70         cb::io::mkdirp(testdir);
71 
72         // create the name of the configuration file to use
73         cfgfile = "test_audit-" + std::to_string(cb_getpid()) + ".json";
74         try {
75             cb::io::rmrf(cfgfile);
76         } catch (std::system_error& e) {
77             if (e.code() != std::error_code(ENOENT, std::system_category())) {
78                 throw e;
79             }
80         }
81 
82         // Start the audit daemon
83         AUDIT_EXTENSION_DATA audit_extension_data;
84         memset(&audit_extension_data, 0, sizeof(audit_extension_data));
85         audit_extension_data.notify_io_complete = notify_io_complete;
86 
87         ASSERT_EQ(AUDIT_SUCCESS,
88                   start_auditdaemon(&audit_extension_data, &auditHandle))
89                 << "start audit daemon: FAILED" << std::endl;
90     }
91 
TearDownTestCase()92     static void TearDownTestCase() {
93         EXPECT_EQ(AUDIT_SUCCESS, shutdown_auditdaemon(auditHandle));
94         cb::io::rmrf(testdir);
95         cb::io::rmrf(cfgfile);
96     }
97 
98 protected:
config_auditd(const std::string & fname)99     void config_auditd(const std::string& fname) {
100         // We don't have a real cookie, but configure_auditdaemon won't call
101         // notify_io_complete unless it's set to a non-null value..
102         // just pass on the ready variable
103         const void* cookie = (const void*)&ready;
104         switch (configure_auditdaemon(auditHandle, fname.c_str(), cookie)) {
105         case AUDIT_SUCCESS:
106             break;
107         case AUDIT_EWOULDBLOCK: {
108             // we have to wait
109             std::unique_lock<std::mutex> lk(mutex);
110             cond.wait(lk, [] { return ready; });
111         }
112             ready = false;
113             break;
114         default:
115             std::cerr << "initialize audit daemon: FAILED" << std::endl;
116             exit(EXIT_FAILURE);
117         };
118     }
119 
SetUp()120     void SetUp() {
121         config.set_descriptors_path(event_descriptor);
122         config.set_rotate_size(2000);
123         config.set_rotate_interval(900);
124         config.set_log_directory(testdir);
125         config.set_auditd_enabled(false);
126         config.set_uuid("12345");
127         config.set_version(2);
128         cb::io::rmrf(testdir);
129         cb::io::mkdirp(testdir);
130     }
131 
enable()132     void enable() {
133         config.set_auditd_enabled(true);
134         cb::io::rmrf(testdir);
135         cb::io::mkdirp(testdir);
136         configure();
137     }
138 
TearDown()139     void TearDown() {
140         config.set_auditd_enabled(false);
141         configure();
142     }
143 
configure()144     void configure() {
145         FILE* fp = fopen(cfgfile.c_str(), "w");
146         ASSERT_NE(nullptr, fp);
147         fprintf(fp, "%s\n", to_string(config.to_json()).c_str());
148         ASSERT_NE(-1, fclose(fp));
149         config_auditd(cfgfile);
150     }
151 
assertNumberOfFiles(size_t num)152     void assertNumberOfFiles(size_t num) {
153         auto vec = cb::io::findFilesContaining(testdir, "");
154         ASSERT_EQ(num, vec.size());
155     }
156 
157     // Test to see if a given string exists in the audit log
158     // @param searchString  string to atempt to find in the audit log
159     // @returns bool  return true if searchString is found in the audit log,
160     //                otherwise return false
existsInAuditLog(const std::string & searchString)161     bool existsInAuditLog(const std::string& searchString) {
162         // confirm that the audit file exists in the directory.
163         assertNumberOfFiles(1);
164         auto vec = cb::io::findFilesContaining(testdir, "");
165         std::ifstream auditFile;
166         auditFile.open(vec.front().c_str());
167         std::string line;
168         while (std::getline(auditFile, line)) {
169             if (line.find(searchString) != std::string::npos) {
170                 auditFile.close();
171                 return true;
172             }
173         }
174         auditFile.close();
175         return false;
176     }
177 
178     MockAuditConfig config;
179     static Audit* auditHandle;
180     static std::string testdir;
181     static std::string cfgfile;
182 };
183 
184 std::string AuditDaemonTest::testdir;
185 std::string AuditDaemonTest::cfgfile;
186 Audit* AuditDaemonTest::auditHandle;
187 
TEST_P(AuditDaemonTest,StartupDisabledDontCreateFiles)188 TEST_P(AuditDaemonTest, StartupDisabledDontCreateFiles) {
189     configure();
190     assertNumberOfFiles(0);
191 }
192 
TEST_P(AuditDaemonTest,StartupEnableCreateFile)193 TEST_P(AuditDaemonTest, StartupEnableCreateFile) {
194     enable();
195     assertNumberOfFiles(1);
196 }
197 
198 class AuditDaemonFilteringTest : public AuditDaemonTest {
199 protected:
SetUp()200     void SetUp() {
201         AuditDaemonTest::SetUp();
202         // Add the userid : {"source" : "internal", "user" : "johndoe"}
203         // to the disabled users list
204         unique_cJSON_ptr disabled_userids(cJSON_CreateObject());
205         cJSON* userIdRoot = cJSON_CreateObject();
206         if (userIdRoot == nullptr) {
207             throw std::runtime_error(
208                     "TestSpecifyDisabledUsers - Error "
209                     "creating cJSON object");
210         }
211         cJSON_AddStringToObject(userIdRoot, "source", "internal");
212         cJSON_AddStringToObject(userIdRoot, "user", "johndoe");
213         cJSON_AddItemToArray(disabled_userids.get(), userIdRoot);
214 
215         config.public_set_disabled_userids(disabled_userids.get());
216     }
217 
218     // Adds a new event that has the filtering_permitted attribute set according
219     // to the input parameter.
220     // @param filteringPermitted  indicates whether the event being added can
221     //                            be filtered or not
addEvent(bool filteringPermitted)222     void addEvent(bool filteringPermitted) {
223         unique_cJSON_ptr root(cJSON_CreateObject());
224         cJSON_AddNumberToObject(root.get(), "id", 1234);
225         cJSON_AddStringToObject(root.get(), "name", "newEvent");
226         cJSON_AddStringToObject(root.get(), "description", "description");
227         cJSON_AddFalseToObject(root.get(), "sync");
228         cJSON_AddTrueToObject(root.get(), "enabled");
229         if (filteringPermitted) {
230             cJSON_AddTrueToObject(root.get(), "filtering_permitted");
231         } else {
232             cJSON_AddFalseToObject(root.get(), "filtering_permitted");
233         }
234         auditHandle->initialize_event_data_structures(root.get());
235     }
236 };
237 
238 /**
239  * Tests the filtering of audit events by user.
240  * An attempt is made to add the new event to the audit log (using
241  * put_audit_event) with a real_userid:user = "johndoe".  Depending on the
242  * global filter setting and the event's filtering permitted attribute, the
243  * "johndoe" event may or may not appear in the audit log.
244  */
TEST_P(AuditDaemonFilteringTest,AuditFilteringTest)245 TEST_P(AuditDaemonFilteringTest, AuditFilteringTest) {
246     bool globalFilterSetting = std::get<0>(GetParam());
247     bool eventFilteringPermitted = std::get<1>(GetParam());
248     bool foundJohndoe{false};
249 
250     const std::string payloadjohndoe =
251             R"({"id": 1234, "timestamp": "test", "real_userid":
252                      {"source": "internal", "user": "johndoe"}})";
253 
254     const std::string payloadanother =
255             R"({"id": 1234, "timestamp": "test", "real_userid":
256                      {"source": "internal", "user": "another"}})";
257 
258     config.set_filtering_enabled(globalFilterSetting);
259     enable();
260     addEvent(eventFilteringPermitted);
261 
262     // generate the 1234 event with real_userid:user = johndoe
263     put_audit_event(
264             auditHandle, 1234, payloadjohndoe.c_str(), payloadjohndoe.length());
265     // generate the 1234 event with real_userid:user = another
266     put_audit_event(
267             auditHandle, 1234, payloadanother.c_str(), payloadanother.length());
268 
269     // Check the audit log exists
270     assertNumberOfFiles(1);
271 
272     // wait up to 10 seconds for "another" to appear in the audit log
273     uint16_t waitIteration = 0;
274     while (!existsInAuditLog("another") && (waitIteration < 200)) {
275         std::this_thread::sleep_for(std::chrono::milliseconds(50));
276         waitIteration++;
277     }
278 
279     // Check to see if "johndoe" exists or not in the audit log
280     foundJohndoe = existsInAuditLog("johndoe");
281 
282     // If filtering is enabled and the event is permitted to be filtered
283     // then the event should not be found in the audit log.
284     if (globalFilterSetting && eventFilteringPermitted) {
285         EXPECT_FALSE(foundJohndoe);
286     } else {
287         // exists in audit log
288         EXPECT_TRUE(foundJohndoe);
289     }
290 }
291 
292 // Check to see if "uuid":"12345" is reported
TEST_F(AuditDaemonTest,UuidTest)293 TEST_F(AuditDaemonTest, UuidTest) {
294     enable();
295     // Check the audit log exists
296     auto vec = cb::io::findFilesContaining(testdir, "");
297     assertNumberOfFiles(1);
298 
299     // wait up to 10 seconds for "uuid" to appear in the audit log
300     uint16_t waitIteration = 0;
301     while (!existsInAuditLog("uuid") && (waitIteration < 200)) {
302         std::this_thread::sleep_for(std::chrono::milliseconds(50));
303         waitIteration++;
304     }
305     EXPECT_TRUE(existsInAuditLog(R"("uuid")"))
306             << "uuid attribute is missing from audit log";
307     EXPECT_TRUE(existsInAuditLog(R"("uuid":"12345")"))
308             << "Wrong uuid in the audit log";
309 }
310 
311 // Check to see if "version":2 is reported
TEST_F(AuditDaemonTest,VersionTest)312 TEST_F(AuditDaemonTest, VersionTest) {
313     enable();
314     // Check the audit log exists
315     auto vec = cb::io::findFilesContaining(testdir, "");
316     assertNumberOfFiles(1);
317 
318     // wait up to 10 seconds for "version" to appear in the audit log
319     uint16_t waitIteration = 0;
320     while (!existsInAuditLog("version") && (waitIteration < 200)) {
321         std::this_thread::sleep_for(std::chrono::milliseconds(50));
322         waitIteration++;
323     }
324     EXPECT_TRUE(existsInAuditLog(R"("version")"))
325             << "version attribute is missing from audit log";
326     EXPECT_TRUE(existsInAuditLog(R"("version":2)"))
327             << "Wrong version in the audit log";
328 }
329 
main(int argc,char ** argv)330 int main(int argc, char** argv) {
331     cb::logger::createConsoleLogger();
332     ::testing::InitGoogleTest(&argc, argv);
333     // required for gethostname(); normally called by memcached's main()
334     cb_initialize_sockets();
335 
336     int cmd;
337     while ((cmd = getopt(argc, argv, "e:")) != EOF) {
338         switch (cmd) {
339         case 'e':
340             event_descriptor = optarg;
341             break;
342         default:
343             std::cerr << "Usage: " << argv[0] << " [-e]" << std::endl
344                       << "\t-e\tPath to audit_events.json" << std::endl;
345             return 1;
346         }
347     }
348 
349     std::string filename = event_descriptor + "/audit_events.json";
350     FILE* fp = fopen(filename.c_str(), "r");
351     if (fp == nullptr) {
352         std::cerr << "Failed to open: " << filename << std::endl;
353         return EXIT_FAILURE;
354     }
355     fclose(fp);
356 
357     return RUN_ALL_TESTS();
358 }
359 
360 static std::vector<bool> allFilteringOptions = {{true, false}};
361 
362 INSTANTIATE_TEST_CASE_P(bool,
363                         AuditDaemonTest,
364                         ::testing::Values(std::make_tuple(true, true)), );
365 INSTANTIATE_TEST_CASE_P(
366         bool,
367         AuditDaemonFilteringTest,
368         ::testing::Combine(::testing::ValuesIn(allFilteringOptions),
369                            ::testing::ValuesIn(allFilteringOptions)), );
370