/src/osquery/plugins/config/parsers/auto_constructed_tables.cpp
Line  | Count  | Source (jump to first uncovered line)  | 
1  |  | /**  | 
2  |  |  * Copyright (c) 2014-present, The osquery authors  | 
3  |  |  *  | 
4  |  |  * This source code is licensed as defined by the LICENSE file found in the  | 
5  |  |  * root directory of this source tree.  | 
6  |  |  *  | 
7  |  |  * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)  | 
8  |  |  */  | 
9  |  |  | 
10  |  | #include <boost/algorithm/string.hpp>  | 
11  |  |  | 
12  |  | #include <osquery/config/config.h>  | 
13  |  | #include <osquery/core/system.h>  | 
14  |  | #include <osquery/core/tables.h>  | 
15  |  | #include <osquery/database/database.h>  | 
16  |  | #include <osquery/filesystem/filesystem.h>  | 
17  |  | #include <osquery/logger/logger.h>  | 
18  |  | #include <osquery/registry/registry_factory.h>  | 
19  |  | #include <osquery/sql/sql.h>  | 
20  |  | #include <osquery/sql/sqlite_util.h>  | 
21  |  | #include <osquery/utils/conversions/join.h>  | 
22  |  | #include <plugins/config/parsers/auto_constructed_tables.h>  | 
23  |  |  | 
24  |  | namespace rj = rapidjson;  | 
25  |  |  | 
26  |  | namespace osquery { | 
27  |  |  | 
28  | 490  | TableAttributes ATCPlugin::attributes() const { | 
29  | 490  |   return table_attributes_;  | 
30  | 490  | }  | 
31  |  |  | 
32  | 612  | void ATCPlugin::setActive() { | 
33  | 612  |   table_attributes_ = TableAttributes::NONE;  | 
34  | 612  | }  | 
35  |  |  | 
36  | 0  | TableRows ATCPlugin::generate(QueryContext& context) { | 
37  | 0  |   TableRows result;  | 
38  | 0  |   std::vector<std::string> paths;  | 
39  | 0  |   auto s = resolveFilePattern(path_, paths);  | 
40  | 0  |   if (!s.ok()) { | 
41  | 0  |     LOG(WARNING) << "ATC Table: Could not glob: " << path_ << " skipping";  | 
42  | 0  |     return result;  | 
43  | 0  |   }  | 
44  | 0  |   for (const auto& path : paths) { | 
45  | 0  |     s = getSqliteJournalMode(path);  | 
46  | 0  |     bool preserve_locking = false;  | 
47  | 0  |     if (!s.ok()) { | 
48  | 0  |       VLOG(1) << "ATC Table: Unable to detect journal mode, applying default "  | 
49  | 0  |                  "locking policy"  | 
50  | 0  |               << " for path " << path;  | 
51  | 0  |     } else { | 
52  | 0  |       preserve_locking = s.getMessage() == "wal";  | 
53  | 0  |     }  | 
54  | 0  |     s = genTableRowsForSqliteTable(  | 
55  | 0  |         path, sqlite_query_, result, preserve_locking);  | 
56  | 0  |     if (!s.ok()) { | 
57  | 0  |       LOG(WARNING) << "ATC Table: Error Code: " << s.getCode()  | 
58  | 0  |                    << " Could not generate data: " << s.getMessage()  | 
59  | 0  |                    << " for path " << path_;  | 
60  | 0  |     }  | 
61  | 0  |   }  | 
62  | 0  |   return result;  | 
63  | 0  | }  | 
64  |  |  | 
65  |  | /// Remove these ATC tables from the registry and database  | 
66  |  | Status ATCConfigParserPlugin::removeATCTables(  | 
67  | 1.54k  |     const std::set<std::string>& detach_tables) { | 
68  | 1.54k  |   auto registry_table = RegistryFactory::get().registry("table"); | 
69  | 1.54k  |   std::set<std::string> failed_tables;  | 
70  | 1.62k  |   for (const auto& table : detach_tables) { | 
71  | 1.62k  |     if (registry_table->exists(table)) { | 
72  | 1.00k  |       std::string value;  | 
73  | 1.00k  |       if (getDatabaseValue(  | 
74  | 1.00k  |               kPersistentSettings, kDatabaseKeyPrefix + table, value)  | 
75  | 1.00k  |               .ok()) { | 
76  | 80  |         registry_table->remove(table);  | 
77  | 80  |         PluginResponse resp;  | 
78  | 80  |         Registry::call(  | 
79  | 80  |             "sql", "sql", {{"action", "detatch"}, {"table", table}}, resp); | 
80  | 80  |         VLOG(1) << "ATC table: " << table << " Removed";  | 
81  | 929  |       } else { | 
82  | 929  |         failed_tables.insert(table);  | 
83  | 929  |       }  | 
84  | 1.00k  |     }  | 
85  | 1.62k  |     deleteDatabaseValue(kPersistentSettings, kDatabaseKeyPrefix + table);  | 
86  | 1.62k  |   }  | 
87  | 1.54k  |   if (failed_tables.empty()) { | 
88  | 616  |     return Status();  | 
89  | 616  |   }  | 
90  | 929  |   return Status(  | 
91  | 929  |       1, "Attempted to remove non ATC tables: " + join(failed_tables, ", "));  | 
92  | 1.54k  | }  | 
93  |  |  | 
94  |  | /// Get all ATC tables that should be registered from the database  | 
95  | 184  | std::set<std::string> ATCConfigParserPlugin::registeredATCTables() { | 
96  | 184  |   std::vector<std::string> tables;  | 
97  | 184  |   scanDatabaseKeys(kPersistentSettings, tables, kDatabaseKeyPrefix);  | 
98  | 184  |   std::set<std::string> set_tables;  | 
99  |  |  | 
100  | 184  |   for (const auto& table : tables) { | 
101  | 152  |     set_tables.insert(table.substr(kDatabaseKeyPrefix.size()));  | 
102  | 152  |   }  | 
103  | 184  |   return set_tables;  | 
104  | 184  | }  | 
105  |  |  | 
106  | 0  | Status ATCConfigParserPlugin::setUp() { | 
107  | 0  |   VLOG(1) << "Removing stale ATC entries";  | 
108  | 0  |   std::vector<std::string> keys;  | 
109  | 0  |   scanDatabaseKeys(kPersistentSettings, keys, kDatabaseKeyPrefix);  | 
110  | 0  |   for (const auto& key : keys) { | 
111  | 0  |     auto s = deleteDatabaseValue(kPersistentSettings, key);  | 
112  | 0  |     if (!s.ok()) { | 
113  | 0  |       LOG(INFO) << "ATC table: Could not clear ATC key " << key  | 
114  | 0  |                 << "from database";  | 
115  | 0  |     }  | 
116  | 0  |   }  | 
117  | 0  |   return Status();  | 
118  | 0  | }  | 
119  |  |  | 
120  |  | Status ATCConfigParserPlugin::update(const std::string& source,  | 
121  | 57.1k  |                                      const ParserConfig& config) { | 
122  | 57.1k  |   auto cv = config.find(kParserKey);  | 
123  | 57.1k  |   if (cv == config.end() || !cv->second.doc().IsObject()) { | 
124  | 56.9k  |     return Status::success();  | 
125  | 56.9k  |   }  | 
126  |  |  | 
127  | 184  |   { | 
128  | 184  |     auto doc = JSON::newObject();  | 
129  | 184  |     auto obj = doc.getObject();  | 
130  | 184  |     doc.copyFrom(cv->second.doc(), obj);  | 
131  | 184  |     doc.add(kParserKey, obj);  | 
132  | 184  |     data_ = std::move(doc);  | 
133  | 184  |   }  | 
134  |  |  | 
135  | 184  |   const auto& ac_tables = data_.doc()[kParserKey];  | 
136  | 184  |   auto tables = RegistryFactory::get().registry("table"); | 
137  | 184  |   auto registered = registeredATCTables();  | 
138  |  |  | 
139  | 2.61k  |   for (const auto& ac_table : ac_tables.GetObject()) { | 
140  | 2.61k  |     if (!ac_table.name.IsString() || !ac_table.value.IsObject()) { | 
141  |  |       // This entry is not formatted correctly.  | 
142  | 159  |       continue;  | 
143  | 159  |     }  | 
144  |  |  | 
145  | 2.45k  |     std::string table_name{ac_table.name.GetString()}; | 
146  | 2.45k  |     auto params = ac_table.value.GetObject();  | 
147  |  |  | 
148  | 2.45k  |     std::string query{params.HasMember("query") && params["query"].IsString() | 
149  | 2.45k  |                           ? params["query"].GetString()  | 
150  | 2.45k  |                           : ""};  | 
151  | 2.45k  |     std::string path{params.HasMember("path") && params["path"].IsString() | 
152  | 2.45k  |                          ? params["path"].GetString()  | 
153  | 2.45k  |                          : ""};  | 
154  | 2.45k  |     std::string platform{params.HasMember("platform") && | 
155  | 2.45k  |                                  params["platform"].IsString()  | 
156  | 2.45k  |                              ? params["platform"].GetString()  | 
157  | 2.45k  |                              : ""};  | 
158  |  |  | 
159  | 2.45k  |     if (query.empty() || path.empty()) { | 
160  | 451  |       LOG(WARNING) << "ATC Table: Skipping " << table_name  | 
161  | 451  |                    << " because it is misconfigured (missing query or path)";  | 
162  | 451  |       continue;  | 
163  | 451  |     }  | 
164  |  |  | 
165  | 2.00k  |     if (!checkPlatform(platform)) { | 
166  | 0  |       VLOG(1) << "ATC table: Skipping " << table_name  | 
167  | 0  |               << " because platform doesn't match";  | 
168  | 0  |       continue;  | 
169  | 0  |     }  | 
170  |  |  | 
171  | 2.00k  |     TableColumns columns;  | 
172  | 2.00k  |     std::string columns_value;  | 
173  | 2.00k  |     columns_value.reserve(256);  | 
174  |  |  | 
175  | 2.00k  |     if (!params.HasMember("columns") || !params["columns"].IsArray()) { | 
176  | 30  |       LOG(WARNING) << "ATC Table: Skipping " << table_name  | 
177  | 30  |                    << " because it is misconfigured (no columns)";  | 
178  | 30  |       continue;  | 
179  | 30  |     }  | 
180  |  |  | 
181  | 1.97k  |     std::string user_defined_path_column;  | 
182  |  |  | 
183  | 2.58k  |     for (const auto& column : params["columns"].GetArray()) { | 
184  | 2.58k  |       if (!column.IsString()) { | 
185  | 3  |         LOG(WARNING) << "ATC Table: " << table_name  | 
186  | 3  |                      << " is misconfigured. (non-string column)";  | 
187  | 3  |         continue;  | 
188  | 3  |       }  | 
189  |  |  | 
190  | 2.58k  |       if (boost::iequals(column.GetString(), "path")) { | 
191  | 1.03k  |         user_defined_path_column = column.GetString();  | 
192  | 1.03k  |       }  | 
193  |  |  | 
194  | 2.58k  |       columns.push_back(make_tuple(  | 
195  | 2.58k  |           std::string(column.GetString()), TEXT_TYPE, ColumnOptions::DEFAULT));  | 
196  | 2.58k  |       columns_value += std::string(column.GetString()) + ",";  | 
197  | 2.58k  |     }  | 
198  |  |  | 
199  | 1.97k  |     if (!user_defined_path_column.empty()) { | 
200  | 516  |       LOG(WARNING) << "ATC Table: " << table_name  | 
201  | 516  |                    << " is misconfigured. The configuration includes `"  | 
202  | 516  |                    << user_defined_path_column  | 
203  | 516  |                    << "`. This is a reserved column name";  | 
204  | 1.45k  |     } else { | 
205  |  |       // Add implicit path column  | 
206  | 1.45k  |       columns.push_back(  | 
207  | 1.45k  |           make_tuple(std::string("path"), TEXT_TYPE, ColumnOptions::DEFAULT)); | 
208  | 1.45k  |       columns_value += "path,";  | 
209  | 1.45k  |     }  | 
210  |  |  | 
211  | 1.97k  |     registered.erase(table_name);  | 
212  | 1.97k  |     std::string table_settings{table_name + query + columns_value + path}; | 
213  | 1.97k  |     std::string old_setting;  | 
214  | 1.97k  |     auto s = getDatabaseValue(  | 
215  | 1.97k  |         kPersistentSettings, kDatabaseKeyPrefix + table_name, old_setting);  | 
216  |  |  | 
217  |  |     // The ATC table hasn't changed so we skip ahead  | 
218  | 1.97k  |     if (table_settings == old_setting) { | 
219  | 429  |       continue;  | 
220  | 429  |     }  | 
221  |  |  | 
222  |  |     // Remove the old table to replace with the new one  | 
223  | 1.54k  |     s = removeATCTables({table_name}); | 
224  | 1.54k  |     if (!s.ok()) { | 
225  | 929  |       LOG(WARNING) << "ATC Table: " << table_name  | 
226  | 929  |                    << " overrides core table; Refusing registration";  | 
227  | 929  |       continue;  | 
228  | 929  |     }  | 
229  |  |  | 
230  | 612  |     s = setDatabaseValue(  | 
231  | 612  |         kPersistentSettings, kDatabaseKeyPrefix + table_name, table_settings);  | 
232  | 612  |     if (!s.ok()) { | 
233  | 0  |       LOG(WARNING) << "ATC Table: " << table_name  | 
234  | 0  |                    << " could not write to database";  | 
235  | 0  |       continue;  | 
236  | 0  |     }  | 
237  |  |  | 
238  | 612  |     auto plugin = std::make_shared<ATCPlugin>(path, columns, query);  | 
239  | 612  |     s = tables->add(table_name, plugin, true);  | 
240  | 612  |     if (!s.ok()) { | 
241  | 0  |       LOG(WARNING) << "ATC Table: " << table_name << ": " << s.getMessage();  | 
242  | 0  |       deleteDatabaseValue(kPersistentSettings, kDatabaseKeyPrefix + table_name);  | 
243  | 0  |       continue;  | 
244  | 0  |     }  | 
245  |  |  | 
246  | 612  |     PluginResponse resp;  | 
247  | 612  |     s = Registry::call(  | 
248  | 612  |         "sql", "sql", {{"action", "attach"}, {"table", table_name}}, resp); | 
249  | 612  |     if (!s.ok()) { | 
250  | 532  |       LOG(WARNING) << "ATC Table: " << table_name << ": " << s.getMessage();  | 
251  | 532  |       deleteDatabaseValue(kPersistentSettings, kDatabaseKeyPrefix + table_name);  | 
252  | 532  |     }  | 
253  | 612  |     plugin->setActive();  | 
254  |  |  | 
255  | 612  |     LOG(INFO) << "ATC table: " << table_name << " Registered";  | 
256  | 612  |   }  | 
257  |  |  | 
258  | 184  |   if (registered.size() > 0) { | 
259  | 4  |     VLOG(1)  | 
260  | 0  |         << "Removing any ATC tables that were removed in this configuration "  | 
261  | 0  |            "change";  | 
262  | 4  |     removeATCTables(registered);  | 
263  | 4  |   }  | 
264  | 184  |   return Status();  | 
265  | 57.1k  | }  | 
266  |  |  | 
267  |  | REGISTER_INTERNAL(ATCConfigParserPlugin,  | 
268  |  |                   "config_parser",  | 
269  |  |                   "auto_constructed_tables");  | 
270  |  | } // namespace osquery  |