/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 |