Skip to content
API Documentation
BastionGuard™
PolicyLoader.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include <algorithm>
6#include <cctype>
7#include <fstream>
8#include <limits>
9#include <set>
10#include <sstream>
11#include <stdexcept>
12#include <string>
13
15namespace {
16
17std::string trim(std::string value)
18{
19 const auto notSpace = [](const unsigned char character) {
20 return !std::isspace(character);
21 };
22
23 value.erase(value.begin(), std::find_if(value.begin(), value.end(), notSpace));
24 value.erase(std::find_if(value.rbegin(), value.rend(), notSpace).base(), value.end());
25 return value;
26}
27
28std::string lower(std::string value)
29{
30 std::transform(value.begin(), value.end(), value.begin(),
31 [](const unsigned char character) {
32 return static_cast<char>(std::tolower(character));
33 });
34 return value;
35}
36
37std::string unquote(std::string value)
38{
39 value = trim(std::move(value));
40 if (value.size() >= 2 &&
41 ((value.front() == '"' && value.back() == '"') ||
42 (value.front() == '\'' && value.back() == '\''))) {
43 value = value.substr(1, value.size() - 2);
44 }
45 return value;
46}
47
48bool parseBool(const std::string& value, const std::size_t line)
49{
50 const std::string normalized = lower(trim(value));
51 if (normalized == "true" || normalized == "yes" || normalized == "1")
52 return true;
53 if (normalized == "false" || normalized == "no" || normalized == "0")
54 return false;
55 throw std::runtime_error("valore booleano non valido alla riga " +
56 std::to_string(line));
57}
58
59Mode parseMode(const std::string& value, const std::size_t line)
60{
61 const std::string normalized = lower(trim(value));
62 if (normalized == "audit")
63 return Mode::Audit;
64 if (normalized == "enforce")
65 return Mode::Enforce;
66 throw std::runtime_error("modalità non valida alla riga " +
67 std::to_string(line));
68}
69
70Action parseAction(const std::string& value, const std::size_t line,
71 const char* keyName)
72{
73 const std::string normalized = lower(trim(value));
74 if (normalized == "audit")
75 return Action::Audit;
76 if (normalized == "block")
77 return Action::Block;
78 throw std::runtime_error(std::string(keyName) +
79 " non valida alla riga " +
80 std::to_string(line));
81}
82
83std::uint32_t parseUint32(const std::string& value, const std::size_t line,
84 const char* keyName)
85{
86 std::size_t consumed = 0;
87 const unsigned long parsed = std::stoul(value, &consumed, 10);
88 if (consumed != value.size() ||
89 parsed > std::numeric_limits<std::uint32_t>::max()) {
90 throw std::runtime_error(std::string(keyName) +
91 " non valido alla riga " +
92 std::to_string(line));
93 }
94 return static_cast<std::uint32_t>(parsed);
95}
96
97std::string normalizedPolicyPath(const std::filesystem::path& path)
98{
99 return path.lexically_normal().string();
100}
101
102void validatePathList(const std::vector<PolicyPath>& entries,
103 const char* label,
104 std::set<std::string>& output)
105{
106 for (const auto& entry : entries) {
107 if (entry.path.empty())
108 throw std::runtime_error(std::string("percorso ") + label + " vuoto");
109 const auto key = normalizedPolicyPath(entry.path);
110 if (!output.insert(key).second)
111 throw std::runtime_error(std::string("percorso ") + label +
112 " duplicato: " + key);
113 }
114}
115
116void validate(const Policy& policy)
117{
118 if (policy.version != 2)
119 throw std::runtime_error("versione policy non supportata: attesa 2");
120
121 if (policy.integrityPollSeconds > 3600)
122 throw std::runtime_error("integrity_poll_seconds deve essere compreso tra 0 e 3600");
123
124 if (policy.globalMetadataSampleLimit > 64)
125 throw std::runtime_error("global_metadata_sample_limit deve essere compreso tra 0 e 64");
126 if (policy.metadataBurstThreshold < 8 || policy.metadataBurstThreshold > 100000)
127 throw std::runtime_error("metadata_burst_threshold deve essere compreso tra 8 e 100000");
128 if (policy.metadataBurstWindowMs < 250 || policy.metadataBurstWindowMs > 60000)
129 throw std::runtime_error("metadata_burst_window_ms deve essere compreso tra 250 e 60000");
131 policy.metadataBurstCooldownMs > 3600000)
132 throw std::runtime_error("metadata_burst_cooldown_ms deve essere >= metadata_burst_window_ms e <= 3600000");
133
134 if (policy.globalMetadataAction == Action::Block) {
135 throw std::runtime_error(
136 "global_metadata_action=block non e supportato: la sorveglianza globale "
137 "deve restare audit-only per non bloccare la home utente; aggiungere gli "
138 "oggetti di sistema a [protected-paths]");
139 }
140
141 if (policy.mode == Mode::Enforce &&
142 (policy.unknownAction == Action::Block ||
144 policy.trustedExecutables.empty()) {
145 throw std::runtime_error(
146 "enforcement con blocco delle elevazioni richiede almeno un eseguibile trusted");
147 }
148
149 std::set<std::string> trustedApplications;
150 std::set<std::string> trusted;
151 std::set<std::string> denied;
152 std::set<std::string> protectedPaths;
153 std::set<std::string> ignoredPaths;
154 validatePathList(policy.trustedApplications, "trusted application", trustedApplications);
155 validatePathList(policy.trustedExecutables, "trusted", trusted);
156 validatePathList(policy.deniedExecutables, "denied", denied);
157 validatePathList(policy.protectedPaths, "protected", protectedPaths);
158 validatePathList(policy.ignoredPaths, "ignored", ignoredPaths);
159
160 for (const auto& key : trusted) {
161 if (denied.count(key) != 0)
162 throw std::runtime_error("percorso presente sia in trusted sia in denied: " + key);
163 }
164 for (const auto& key : trustedApplications) {
165 if (denied.count(key) != 0)
166 throw std::runtime_error("applicazione presente sia in trusted-applications sia in denied: " + key);
167 }
168}
169
170} // namespace
171
172Policy PolicyLoader::load(const std::filesystem::path& path)
173{
174 return loadFromText(PolicySecurity::read(path, false), path);
175}
176
177Policy PolicyLoader::loadFromText(const std::string& content,
178 const std::filesystem::path& source)
179{
180 std::istringstream input(content);
181 Policy policy;
182 std::string section;
183 std::string line;
184 std::size_t lineNumber = 0;
185 std::uint32_t nextRuleId = 1;
186
187 while (std::getline(input, line)) {
188 ++lineNumber;
189 line = trim(std::move(line));
190 if (line.empty() || line.front() == '#' || line.front() == ';')
191 continue;
192
193 if (line.front() == '[' && line.back() == ']') {
194 section = lower(trim(line.substr(1, line.size() - 2)));
195 continue;
196 }
197
198 const auto separator = line.find('=');
199 if (separator == std::string::npos)
200 throw std::runtime_error("riga policy non valida: " +
201 std::to_string(lineNumber));
202
203 const std::string key = lower(trim(line.substr(0, separator)));
204 const std::string value = unquote(line.substr(separator + 1));
205
206 if (section == "engine") {
207 if (key == "version") {
208 policy.version = parseUint32(value, lineNumber, "version");
209 } else if (key == "mode") {
210 policy.mode = parseMode(value, lineNumber);
211 } else if (key == "unknown_action") {
212 policy.unknownAction = parseAction(value, lineNumber, "unknown_action");
213 } else if (key == "metadata_action") {
214 policy.metadataAction = parseAction(value, lineNumber, "metadata_action");
215 } else if (key == "global_metadata_action") {
216 policy.globalMetadataAction =
217 parseAction(value, lineNumber, "global_metadata_action");
218 } else if (key == "credential_anomaly_action") {
220 parseAction(value, lineNumber, "credential_anomaly_action");
221 } else if (key == "allow_missing_paths") {
222 policy.allowMissingPaths = parseBool(value, lineNumber);
223 } else if (key == "require_root_owned_trusted_executables") {
224 policy.requireRootOwnedTrustedExecutables = parseBool(value, lineNumber);
225 } else if (key == "protect_trusted_executables") {
226 policy.protectTrustedExecutables = parseBool(value, lineNumber);
227 } else if (key == "monitor_global_privilege_metadata") {
228 policy.monitorGlobalPrivilegeMetadata = parseBool(value, lineNumber);
229 } else if (key == "detect_global_metadata_bursts") {
230 policy.detectGlobalMetadataBursts = parseBool(value, lineNumber);
231 } else if (key == "global_metadata_sample_limit") {
232 policy.globalMetadataSampleLimit = parseUint32(value, lineNumber, "global_metadata_sample_limit");
233 } else if (key == "metadata_burst_threshold") {
234 policy.metadataBurstThreshold = parseUint32(value, lineNumber, "metadata_burst_threshold");
235 } else if (key == "metadata_burst_window_ms") {
236 policy.metadataBurstWindowMs = parseUint32(value, lineNumber, "metadata_burst_window_ms");
237 } else if (key == "metadata_burst_cooldown_ms") {
238 policy.metadataBurstCooldownMs = parseUint32(value, lineNumber, "metadata_burst_cooldown_ms");
239 } else if (key == "ignore_run_global_metadata") {
240 policy.ignoreRunGlobalMetadata = parseBool(value, lineNumber);
241 } else if (key == "log_trusted") {
242 policy.logTrusted = parseBool(value, lineNumber);
243 } else if (key == "auto_block_system_services") {
244 policy.autoBlockSystemServices = parseBool(value, lineNumber);
245 } else if (key == "block_user_home") {
246 policy.blockUserHome = parseBool(value, lineNumber);
247 } else if (key == "integrity_poll_seconds") {
248 policy.integrityPollSeconds =
249 parseUint32(value, lineNumber, "integrity_poll_seconds");
250 } else if (key == "event_log") {
251 policy.eventLog = value;
252 } else {
253 throw std::runtime_error("chiave engine sconosciuta alla riga " +
254 std::to_string(lineNumber) + ": " + key);
255 }
256 } else if (section == "trusted-applications" ||
257 section == "trusted-executables" ||
258 section == "denied-executables" ||
259 section == "protected-paths" ||
260 section == "ignored-paths") {
261 const bool recursiveKey =
262 key == "recursive_path" || key == "required_recursive_path";
263 const bool regularKey = key == "path" || key == "required_path";
264 if (!regularKey && !recursiveKey)
265 throw std::runtime_error("chiave policy sconosciuta alla riga " +
266 std::to_string(lineNumber) + ": " + key);
267 if (recursiveKey &&
268 section != "protected-paths" &&
269 section != "ignored-paths") {
270 throw std::runtime_error(
271 "le regole ricorsive sono ammesse soltanto in [protected-paths] "
272 "o [ignored-paths] alla riga " +
273 std::to_string(lineNumber));
274 }
275
276 const bool required =
277 key == "required_path" || key == "required_recursive_path";
278 PolicyPath entry{nextRuleId++, value, required, recursiveKey};
279 if (section == "trusted-applications")
280 policy.trustedApplications.push_back(std::move(entry));
281 else if (section == "trusted-executables")
282 policy.trustedExecutables.push_back(std::move(entry));
283 else if (section == "denied-executables")
284 policy.deniedExecutables.push_back(std::move(entry));
285 else if (section == "protected-paths")
286 policy.protectedPaths.push_back(std::move(entry));
287 else
288 policy.ignoredPaths.push_back(std::move(entry));
289 } else {
290 throw std::runtime_error("sezione policy mancante o sconosciuta alla riga " +
291 std::to_string(lineNumber));
292 }
293 }
294
295 try {
296 validate(policy);
297 } catch (const std::exception& error) {
298 throw std::runtime_error(source.string() + ": " + error.what());
299 }
300 return policy;
301}
302
303} // namespace bastionguard::rootguard
static Policy load(const std::filesystem::path &path)
static Policy loadFromText(const std::string &content, const std::filesystem::path &source="<memory>")
static std::string read(const std::filesystem::path &path, bool requireSecureOwnership)
void validatePathList(const std::vector< PolicyPath > &entries, const char *label, std::set< std::string > &output)
std::string normalizedPolicyPath(const std::filesystem::path &path)
bool parseBool(const std::string &value, const std::size_t line)
Action parseAction(const std::string &value, const std::size_t line, const char *keyName)
Mode parseMode(const std::string &value, const std::size_t line)
std::uint32_t parseUint32(const std::string &value, const std::size_t line, const char *keyName)
std::vector< PolicyPath > trustedExecutables
Definition Policy.hpp:56
std::vector< PolicyPath > deniedExecutables
Definition Policy.hpp:57
std::uint32_t metadataBurstThreshold
Definition Policy.hpp:44
std::vector< PolicyPath > ignoredPaths
Definition Policy.hpp:59
std::uint32_t globalMetadataSampleLimit
Definition Policy.hpp:43
std::filesystem::path eventLog
Definition Policy.hpp:54
std::uint32_t integrityPollSeconds
Definition Policy.hpp:53
std::uint32_t metadataBurstCooldownMs
Definition Policy.hpp:46
std::vector< PolicyPath > protectedPaths
Definition Policy.hpp:58
std::uint32_t metadataBurstWindowMs
Definition Policy.hpp:45
std::vector< PolicyPath > trustedApplications
Definition Policy.hpp:55