[pkg-nagios-changes] [Git][nagios-team/icinga2][master] 4 commits: New upstream version 2.16.5
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Tue Aug 18 14:18:04 BST 2026
Bas Couwenberg pushed to branch master at Debian Nagios Maintainer Group / icinga2
Commits:
8639d086 by Bas Couwenberg at 2026-08-18T14:47:32+02:00
New upstream version 2.16.5
- - - - -
0dddee92 by Bas Couwenberg at 2026-08-18T14:48:00+02:00
Update upstream source from tag 'upstream/2.16.5'
Update to upstream version '2.16.5'
with Debian dir b518a74232ebf1e2bbe004304f97f08f19650fcd
- - - - -
4072ab1c by Bas Couwenberg at 2026-08-18T14:48:09+02:00
New upstream release.
- - - - -
158318e2 by Bas Couwenberg at 2026-08-18T14:49:38+02:00
Set distribution to unstable.
- - - - -
22 changed files:
- .github/workflows/authors-file.yml
- .github/workflows/backbot.yml
- .github/workflows/linux.yml
- .github/workflows/windows.yml
- CHANGELOG.md
- ICINGA2_VERSION
- debian/changelog
- lib/icingadb/icingadb-objects.cpp
- lib/icingadb/icingadb.cpp
- lib/icingadb/icingadb.hpp
- lib/remote/apilistener-configsync.cpp
- lib/remote/endpoint.cpp
- lib/remote/endpoint.hpp
- lib/remote/eventqueue.cpp
- lib/remote/eventqueue.hpp
- lib/remote/eventshandler.cpp
- lib/remote/filterutility.cpp
- lib/remote/filterutility.hpp
- lib/remote/jsonrpc.hpp
- lib/remote/jsonrpcconnection-pki.cpp
- lib/remote/jsonrpcconnection.cpp
- lib/remote/pkiutility.cpp
Changes:
=====================================
.github/workflows/authors-file.yml
=====================================
@@ -10,7 +10,7 @@ jobs:
steps:
- name: Checkout HEAD
- uses: actions/checkout at v6
+ uses: actions/checkout at v7
with:
fetch-depth: 0
=====================================
.github/workflows/backbot.yml
=====================================
@@ -38,7 +38,7 @@ jobs:
permission-issues: write # Needed to add comments to the PRs created by Backbot and the original PR.
- name: Checkout
- uses: actions/checkout at v6
+ uses: actions/checkout at v7
with:
token: ${{ steps.backbot-token.outputs.token }} # To make authenticated git operations.
sha: ${{ github.event.pull_request.head.sha }} # Checkout the latest commit of the merged PR.
=====================================
.github/workflows/linux.yml
=====================================
@@ -33,21 +33,18 @@ jobs:
- debian:12
- debian:13
- - fedora:41
- fedora:42
- fedora:43
- fedora:44
- fedora:45
- - opensuse/leap:15.6
- - opensuse/leap:16.0
-
# We don't actually support Rocky Linux as such!
# We just use that RHEL clone to test the original.
- rockylinux:8
- rockylinux:9
- rockylinux/rockylinux:10
+ # openSUSE Leap is close enough to SLES to test just one of them.
- registry.suse.com/suse/sle15:15.6
- registry.suse.com/suse/sle15:15.7
- registry.suse.com/bci/bci-base:16.0
@@ -69,13 +66,13 @@ jobs:
steps:
- name: Checkout HEAD
- uses: actions/checkout at v6
+ uses: actions/checkout at v7
- name: Turn on Problem Matcher
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
- name: Restore/backup ccache
- uses: actions/cache at v5
+ uses: actions/cache at v6
with:
path: ccache
key: ccache/${{ matrix.distro }}
=====================================
.github/workflows/windows.yml
=====================================
@@ -29,7 +29,7 @@ jobs:
steps:
- name: Checkout HEAD
- uses: actions/checkout at v6
+ uses: actions/checkout at v7
with:
fetch-depth: 0
=====================================
CHANGELOG.md
=====================================
@@ -7,6 +7,33 @@ documentation before upgrading to a new release.
Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed).
+## 2.16.5 (2026-08-18)
+
+This release contains a bugfix for a regression in the `IcingaDB` feature that was introduced in v2.16.0 and some
+security enhancements that fix a couple of minor vulnerabilities.
+
+One vulnerability allowed an authenticated `ApiUser` with the `events/*` permission to obtain information about
+objects through crafted filter expressions, without holding the relevant `objects/query/*` permission. The fix is to
+correctly apply permissions to filter expressions on `/v1/events`.
+
+The other vulnerability allowed an authenticated Icinga 2 node to use the ~1 GB message limit to possibly crash another
+node through memory exhaustion. Depending on available memory, multiple compromised nodes may be required for
+a successful attack since a node can only make one connection. The fix applies a 16 MiB limit to messages from nodes
+lower in the hierarchy.
+
+### Security Enhancements
+
+* Apply user permissions to filter expressions for `/v1/events` similarly to `/v1/objects`
+ ([GHSA-v265-w3gm-99vg](https://github.com/Icinga/icinga2/security/advisories/GHSA-v265-w3gm-99vg))
+* Introduce an additional 16 MiB message size limit on all child-zone connections
+ ([GHSA-wm63-p2jg-5665](https://github.com/Icinga/icinga2/security/advisories/GHSA-wm63-p2jg-5665))
+* Don't include sensitive certificate request tickets in log messages (#10960)
+* Don't log full object config containing potentially sensitive information (#10988)
+
+### Bugfixes
+
+* IcingaDB: Fix multiple potential race conditions during initial config dump (#10981)
+
## 2.16.4 (2026-07-16)
This release contains a number of fixes for various smaller but annoying bugs, including one regression regarding API
=====================================
ICINGA2_VERSION
=====================================
@@ -1,2 +1,2 @@
-Version: 2.16.4
+Version: 2.16.5
Revision: 1
=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+icinga2 (2.16.5-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Bas Couwenberg <sebastic at debian.org> Tue, 18 Aug 2026 14:49:23 +0200
+
icinga2 (2.16.4-1) unstable; urgency=medium
* New upstream release.
=====================================
lib/icingadb/icingadb-objects.cpp
=====================================
@@ -210,6 +210,11 @@ void IcingaDB::ConfigStaticInitialize()
void IcingaDB::UpdateAllConfigObjects()
{
+ // Snapshot the current number of reconnections made by any of the RedisConnections.
+ // This is compared to the current value at the end of the function to verify that no
+ // reconnects occurred.
+ auto snapReconnectCount = m_ReconnectCount->load(std::memory_order_acquire);
+
// This function performs an initial dump of all configuration objects into Redis, thus there are no
// previously enqueued queries on m_RconWorker that we need to wait for. So, no Sync() call is necessary here.
m_RconWorker->FireAndForgetQuery({"XADD", "icinga:schema", "MAXLEN", "1", "*", "version", "6"}, {}, true);
@@ -513,13 +518,8 @@ void IcingaDB::UpdateAllConfigObjects()
if (upq.HasExceptions()) {
for (std::exception_ptr exc : upq.GetExceptions()) {
- try {
- if (exc) {
- std::rethrow_exception(exc);
- }
- } catch(const std::exception& e) {
- Log(LogCritical, "IcingaDB")
- << "Exception during ConfigDump: " << e.what();
+ if (exc) {
+ std::rethrow_exception(exc);
}
}
}
@@ -530,10 +530,21 @@ void IcingaDB::UpdateAllConfigObjects()
m_RconWorker->FireAndForgetQuery({"XADD", "icinga:dump", "*", "key", "*", "state", "done"});
- // enqueue a callback that will notify us once all previous queries were executed and wait for this event
- std::promise<void> p;
- m_RconWorker->EnqueueCallback([&p](boost::asio::yield_context&) { p.set_value(); });
- p.get_future().wait();
+ /* The other connections already get a final sync in their workqueue above, so we sync just
+ * the `m_RconWorker` here, to ensure that all requests have made it through the queue.
+ * This also puts a final check to the connection, since it would throw if the connection
+ * went down in the meantime.
+ */
+ m_RconWorker->Sync();
+
+ /* Since now all connections were synced after all transactions have completed, we can now compare
+ * the current reconnect count to the snapshot we made at the start of this function. If the count
+ * matches we know for sure that no reconnects on any of the connections lead to an inconsistent
+ * sync. If reconnects did occur, this throws and the update is repeated again.
+ */
+ if (m_ReconnectCount->load(std::memory_order_acquire) != snapReconnectCount) {
+ BOOST_THROW_EXCEPTION(std::runtime_error{"Lost connection to Redis during ConfigDump"});
+ }
auto endTime (Utility::GetTime());
auto took (endTime - startTime);
=====================================
lib/icingadb/icingadb.cpp
=====================================
@@ -84,6 +84,8 @@ void IcingaDB::Start(bool runtimeCreated)
m_RconLocked.store(m_Rcon);
m_RconWorker = new RedisConnection(connInfo, m_Rcon);
+ std::vector<RedisConnection::Ptr> childConns;
+ childConns.push_back(m_RconWorker);
for (const auto& [type, _] : GetSyncableTypes()) {
auto ctype (dynamic_cast<ConfigType*>(type.get()));
@@ -91,28 +93,32 @@ void IcingaDB::Start(bool runtimeCreated)
continue;
RedisConnection::Ptr con = new RedisConnection(connInfo, m_Rcon);
+ childConns.push_back(con);
- con->SetConnectedCallback([this, con](boost::asio::yield_context&) {
- con->SetConnectedCallback(nullptr);
+ m_Rcons[ctype] = std::move(con);
+ }
+
+ auto pendingConns = std::make_shared<std::atomic_size_t>(childConns.size());
- size_t pending = --m_PendingRcons;
+ for (auto & conn : childConns) {
+ conn->SetConnectedCallback([this, pendingConns, conn](boost::asio::yield_context&) {
+ conn->SetConnectedCallback([reconnectCount = m_ReconnectCount](boost::asio::yield_context&) {
+ reconnectCount->fetch_add(1, std::memory_order_release);
+ });
+
+ auto pending = --*pendingConns;
Log(LogDebug, "IcingaDB") << pending << " pending child connections remaining";
if (pending == 0) {
m_WorkQueue.Enqueue([this]() { OnConnectedHandler(); });
}
});
-
- m_Rcons[ctype] = std::move(con);
}
- m_PendingRcons = m_Rcons.size();
-
- m_Rcon->SetConnectedCallback([this](boost::asio::yield_context&) {
+ m_Rcon->SetConnectedCallback([this, childConns = std::move(childConns)](boost::asio::yield_context&) {
m_Rcon->SetConnectedCallback(nullptr);
- m_RconWorker->Start();
- for (auto& kv : m_Rcons) {
- kv.second->Start();
+ for (const auto& conn : childConns) {
+ conn->Start();
}
});
m_Rcon->Start();
@@ -149,7 +155,15 @@ void IcingaDB::OnConnectedHandler()
m_ConfigDumpInProgress = true;
PublishStats();
- UpdateAllConfigObjects();
+ while (true) {
+ try {
+ UpdateAllConfigObjects();
+ break;
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "IcingaDB") << "Exception during ConfigDump: " << ex.what();
+ }
+ Utility::Sleep(10);
+ }
m_ConfigDumpDone.store(true);
m_ConfigDumpInProgress = false;
=====================================
lib/icingadb/icingadb.hpp
=====================================
@@ -456,7 +456,7 @@ private:
*/
RedisConnection::Ptr m_RconWorker;
std::unordered_map<ConfigType*, RedisConnection::Ptr> m_Rcons;
- std::atomic_size_t m_PendingRcons;
+ std::shared_ptr<std::atomic_uint> m_ReconnectCount = std::make_shared<std::atomic_uint>(0);
struct {
DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage, DependencyGroup;
=====================================
lib/remote/apilistener-configsync.cpp
=====================================
@@ -42,9 +42,6 @@ void ApiListener::ConfigUpdateObjectHandler(const ConfigObject::Ptr& object, con
Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
{
- Log(LogNotice, "ApiListener")
- << "Received config update for object: " << JsonEncode(params);
-
/* check permissions */
ApiListener::Ptr listener = ApiListener::GetInstance();
@@ -108,6 +105,10 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin
return Empty;
}
+ Log(LogNotice, "ApiListener")
+ << "Received config update for object '" << objName << "' of type " << objType
+ << ", version: " << std::fixed << objVersion;
+
Type::Ptr ptype = Type::GetByName(objType);
auto *ctype = dynamic_cast<ConfigType *>(ptype.get());
=====================================
lib/remote/endpoint.cpp
=====================================
@@ -182,3 +182,14 @@ double Endpoint::GetSecondsProcessingMessages() const
{
return m_InputProcessingTime;
}
+
+ssize_t Endpoint::GetMessageReceiveSizeLimit() const
+{
+ // Parent and sibling nodes are trusted
+ // to send messages of unbounded size, e.g. config syncs
+ if (Zone::GetLocalZone()->IsChildOf(m_Zone)) {
+ return -1;
+ }
+
+ return 16L * 1024 * 1024;
+}
=====================================
lib/remote/endpoint.hpp
=====================================
@@ -62,6 +62,8 @@ public:
double GetSecondsProcessingMessages() const override;
+ ssize_t GetMessageReceiveSizeLimit() const;
+
protected:
void OnAllConfigLoaded() override;
=====================================
lib/remote/eventqueue.cpp
=====================================
@@ -130,8 +130,8 @@ std::map<String, EventsInbox::Filter> EventsInbox::m_Filters ({{"", EventsInbox:
EventsRouter EventsRouter::m_Instance;
-EventsInbox::EventsInbox(String filter, const String& filterSource)
- : m_Timer(IoEngine::Get().GetIoContext())
+EventsInbox::EventsInbox(String filter, const String& filterSource, ApiUser::Ptr user)
+ : m_User(std::move(user)), m_Timer(IoEngine::Get().GetIoContext())
{
std::unique_lock<std::mutex> lock (m_FiltersMutex);
m_Filter = m_Filters.find(filter);
@@ -169,6 +169,11 @@ const Expression::Ptr& EventsInbox::GetFilter()
return m_Filter->second.Expr;
}
+const ApiUser::Ptr& EventsInbox::GetUser() const noexcept
+{
+ return m_User;
+}
+
void EventsInbox::Push(Dictionary::Ptr event)
{
std::unique_lock<std::mutex> lock (m_Mutex);
@@ -214,8 +219,8 @@ Dictionary::Ptr EventsInbox::Shift(boost::asio::yield_context yc, double timeout
return event;
}
-EventsSubscriber::EventsSubscriber(std::set<EventType> types, String filter, const String& filterSource)
- : m_Types(std::move(types)), m_Inbox(new EventsInbox(std::move(filter), filterSource))
+EventsSubscriber::EventsSubscriber(std::set<EventType> types, String filter, const String& filterSource, ApiUser::Ptr user)
+ : m_Types(std::move(types)), m_Inbox(new EventsInbox(std::move(filter), filterSource, std::move(user)))
{
EventsRouter::GetInstance().Subscribe(m_Types, m_Inbox);
}
@@ -244,22 +249,32 @@ void EventsFilter::Push(Dictionary::Ptr event)
{
for (auto& perFilter : m_Inboxes) {
if (perFilter.first) {
- ScriptFrame frame(true, new Namespace());
- frame.Sandboxed = true;
-
- try {
- if (!FilterUtility::EvaluateFilter(frame, perFilter.first.get(), event, "event")) {
- continue;
+ /* Each subscriber may hold different permissions, so the filter has to be evaluated
+ * separately per inbox using a checker bound to that inbox's own user, even though
+ * several inboxes here share the same compiled filter expression. A fresh checker is
+ * created per evaluation since inboxes are shared across concurrently dispatching
+ * threads and the checker's internal permission cache is not thread-safe.
+ */
+ for (auto& inbox : perFilter.second) {
+ ScriptFrame frame(true, new Namespace());
+
+ frame.Sandboxed = true;
+ frame.PermChecker = new FilterExprPermissionChecker(inbox->GetUser());
+
+ try {
+ if (FilterUtility::EvaluateFilter(frame, perFilter.first.get(), event, "event")) {
+ inbox->Push(event);
+ }
+ } catch (const std::exception& ex) {
+ Log(LogWarning, "EventQueue")
+ << "Error occurred while evaluating event filter for queue of user '"
+ << inbox->GetUser()->GetName() << "': " << DiagnosticInformation(ex);
}
- } catch (const std::exception& ex) {
- Log(LogWarning, "EventQueue")
- << "Error occurred while evaluating event filter for queue: " << DiagnosticInformation(ex);
- continue;
}
- }
-
- for (auto& inbox : perFilter.second) {
- inbox->Push(event);
+ } else {
+ for (auto& inbox : perFilter.second) {
+ inbox->Push(event);
+ }
}
}
}
=====================================
lib/remote/eventqueue.hpp
=====================================
@@ -86,7 +86,7 @@ class EventsInbox : public Object
public:
DECLARE_PTR_TYPEDEFS(EventsInbox);
- EventsInbox(String filter, const String& filterSource);
+ EventsInbox(String filter, const String& filterSource, ApiUser::Ptr user);
EventsInbox(const EventsInbox&) = delete;
EventsInbox(EventsInbox&&) = delete;
EventsInbox& operator=(const EventsInbox&) = delete;
@@ -94,6 +94,7 @@ public:
~EventsInbox();
const Expression::Ptr& GetFilter();
+ const ApiUser::Ptr& GetUser() const noexcept;
void Push(Dictionary::Ptr event);
Dictionary::Ptr Shift(boost::asio::yield_context yc, double timeout = 5);
@@ -110,6 +111,7 @@ private:
std::mutex m_Mutex;
decltype(m_Filters.begin()) m_Filter;
+ ApiUser::Ptr m_User;
std::queue<Dictionary::Ptr> m_Queue;
boost::asio::deadline_timer m_Timer;
};
@@ -117,7 +119,7 @@ private:
class EventsSubscriber
{
public:
- EventsSubscriber(std::set<EventType> types, String filter, const String& filterSource);
+ EventsSubscriber(std::set<EventType> types, String filter, const String& filterSource, ApiUser::Ptr user);
EventsSubscriber(const EventsSubscriber&) = delete;
EventsSubscriber(EventsSubscriber&&) = delete;
EventsSubscriber& operator=(const EventsSubscriber&) = delete;
=====================================
lib/remote/eventshandler.cpp
=====================================
@@ -103,7 +103,7 @@ bool EventsHandler::HandleRequest(
filter = HttpUtility::GetLastParameter(params, "filter");
}
- EventsSubscriber subscriber (std::move(eventTypes), std::move(filter), l_ApiQuery);
+ EventsSubscriber subscriber (std::move(eventTypes), std::move(filter), l_ApiQuery, user);
response.result(http::status::ok);
response.set(http::field::content_type, "application/json");
=====================================
lib/remote/filterutility.cpp
=====================================
@@ -26,110 +26,95 @@ Dictionary::Ptr FilterUtility::GetTargetForVar(const String& name, const Value&
});
}
+FilterExprPermissionChecker::FilterExprPermissionChecker(ApiUser::Ptr user) : m_User(std::move(user))
+{
+}
+
/**
- * Controls access to an object or variable based on an ApiUser's permissions.
+ * Check if the user has the given permission and cache the result if they do.
+ *
+ * This is a wrapper around FilterUtility::CheckPermission() that caches the generated
+ * filter expression for later use when checking permissions inside sandboxed ScriptFrames.
+ *
+ * Like FilterUtility::CheckPermission() an exception is thrown if the user does not have
+ * the requested permission.
+ *
+ * If the user has permission and there is a filter for the given permission, the filter
+ * expression is generated, cached and then a pointer to it is returned, otherwise a
+ * nullptr will be returned.
+ *
+ * Since the optionally returned pointer is a raw-pointer and this class retains ownership
+ * over the expression it is only valid for the lifetime of the @c FilterExprPermissionChecker
+ * object that returned it.
+ *
+ * @param permissionString The permission string to check against the ApiUser member of this class.
*
- * This is accomplished by caching the generated filter expressions so they don't have to be
- * regenerated again and again when access is repeatedly checked in script functions and when
- * evaluating expressions.
+ * @return a pointer to the generated permission expression if the permission has a filter, or nullptr if not.
*/
-class FilterExprPermissionChecker : public ScriptPermissionChecker
+Expression* FilterExprPermissionChecker::CheckPermission(const String& permissionString)
{
-public:
- DECLARE_PTR_TYPEDEFS(FilterExprPermissionChecker);
-
- explicit FilterExprPermissionChecker(ApiUser::Ptr user) : m_User(std::move(user)) {}
-
- /**
- * Check if the user has the given permission and cache the result if they do.
- *
- * This is a wrapper around FilterUtility::CheckPermission() that caches the generated
- * filter expression for later use when checking permissions inside sandboxed ScriptFrames.
- *
- * Like FilterUtility::CheckPermission() an exception is thrown if the user does not have
- * the requested permission.
- *
- * If the user has permission and there is a filter for the given permission, the filter
- * expression is generated, cached and then a pointer to it is returned, otherwise a
- * nullptr will be returned.
- *
- * Since the optionally returned pointer is a raw-pointer and this class retains ownership
- * over the expression it is only valid for the lifetime of the @c FilterExprPermissionChecker
- * object that returned it.
- *
- * @param permissionString The permission string to check against the ApiUser member of this class.
- *
- * @return a pointer to the generated permission expression if the permission has a filter, or nullptr if not.
- */
- Expression* CheckPermission(const String& permissionString)
- {
- auto [it, inserted] = m_PermCache.try_emplace(permissionString);
- auto& [hasPermission, permissionExpr] = it->second;
-
- if (inserted) {
- FilterUtility::CheckPermission(m_User, permissionString, &permissionExpr);
- } else if (!hasPermission) {
- BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permissionString.ToLower()));
- }
+ auto [it, inserted] = m_PermCache.try_emplace(permissionString);
+ auto& [hasPermission, permissionExpr] = it->second;
- hasPermission = true;
- return permissionExpr.get();
+ if (inserted) {
+ FilterUtility::CheckPermission(m_User, permissionString, &permissionExpr);
+ } else if (!hasPermission) {
+ BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permissionString.ToLower()));
}
- /**
- * Checks if this object's ApiUser has permissions to access variable `varName`.
- *
- * @param varName The name of the variable to check for access
- *
- * @return 'true' if the variable can be accessed, 'false' if it can't.
- */
- bool CanAccessGlobalVariable(const String& varName) override
- {
- auto obj = FilterUtility::GetTargetForVar(varName, ScriptGlobal::Get(varName));
- return CheckPermissionAndEvalFilter("variables", obj, "variable");
- }
+ hasPermission = true;
+ return permissionExpr.get();
+}
- /**
- * Checks if this object's ApiUser has permissions to access ConfigObject `obj`.
- *
- * @param obj A pointer to the ConfigObject to check for access
- *
- * @return 'true' if the object can be accessed, 'false' if it can't.
- */
- bool CanAccessConfigObject(const ConfigObject::Ptr& obj) override
- {
- ASSERT(obj);
-
- String perm = "objects/query/" + obj->GetReflectionType()->GetName();
- String varName = obj->GetReflectionType()->GetName().ToLower();
-
- return CheckPermissionAndEvalFilter(perm, obj, varName);
- }
+/**
+ * Checks if this object's ApiUser has permissions to access variable `varName`.
+ *
+ * @param varName The name of the variable to check for access
+ *
+ * @return 'true' if the variable can be accessed, 'false' if it can't.
+ */
+bool FilterExprPermissionChecker::CanAccessGlobalVariable(const String& varName)
+{
+ auto obj = FilterUtility::GetTargetForVar(varName, ScriptGlobal::Get(varName));
+ return CheckPermissionAndEvalFilter("variables", obj, "variable");
+}
-private:
- bool CheckPermissionAndEvalFilter(const String& permissionString, const Object::Ptr& obj, const String& varName)
- {
- auto [it, inserted] = m_PermCache.try_emplace(permissionString);
- auto& [hasPermission, permissionExpr] = it->second;
+/**
+ * Checks if this object's ApiUser has permissions to access ConfigObject `obj`.
+ *
+ * @param obj A pointer to the ConfigObject to check for access
+ *
+ * @return 'true' if the object can be accessed, 'false' if it can't.
+ */
+bool FilterExprPermissionChecker::CanAccessConfigObject(const ConfigObject::Ptr& obj)
+{
+ ASSERT(obj);
- if (inserted) {
- hasPermission = FilterUtility::HasPermission(m_User, permissionString, &permissionExpr);
- }
+ String perm = "objects/query/" + obj->GetReflectionType()->GetName();
+ String varName = obj->GetReflectionType()->GetName().ToLower();
- if (hasPermission && permissionExpr) {
- ScriptFrame permissionFrame(false, new Namespace());
- // Sandboxing is lifted because this only evaluates the function from the
- // ApiUser->permissions->filter
- permissionFrame.Sandboxed = false;
- return FilterUtility::EvaluateFilter(permissionFrame, permissionExpr.get(), obj, varName);
- }
+ return CheckPermissionAndEvalFilter(perm, obj, varName);
+}
+
+bool FilterExprPermissionChecker::CheckPermissionAndEvalFilter(const String& permissionString, const Object::Ptr& obj, const String& varName)
+{
+ auto [it, inserted] = m_PermCache.try_emplace(permissionString);
+ auto& [hasPermission, permissionExpr] = it->second;
- return hasPermission;
+ if (inserted) {
+ hasPermission = FilterUtility::HasPermission(m_User, permissionString, &permissionExpr);
}
- std::unordered_map<String, std::pair<bool, std::unique_ptr<Expression>>> m_PermCache;
- ApiUser::Ptr m_User;
-};
+ if (hasPermission && permissionExpr) {
+ ScriptFrame permissionFrame(false, new Namespace());
+ // Sandboxing is lifted because this only evaluates the function from the
+ // ApiUser->permissions->filter
+ permissionFrame.Sandboxed = false;
+ return FilterUtility::EvaluateFilter(permissionFrame, permissionExpr.get(), obj, varName);
+ }
+
+ return hasPermission;
+}
Type::Ptr FilterUtility::TypeFromPluralName(const String& pluralName)
{
=====================================
lib/remote/filterutility.hpp
=====================================
@@ -75,6 +75,31 @@ class MissingPermissionError : public ScriptError
using ScriptError::ScriptError;
};
+/**
+ * Controls access to an object or variable based on an ApiUser's permissions.
+ *
+ * This is accomplished by caching the generated filter expressions so they don't have to be
+ * regenerated again and again when access is repeatedly checked in script functions and when
+ * evaluating expressions.
+ */
+class FilterExprPermissionChecker : public ScriptPermissionChecker
+{
+public:
+ DECLARE_PTR_TYPEDEFS(FilterExprPermissionChecker);
+
+ explicit FilterExprPermissionChecker(ApiUser::Ptr user);
+
+ Expression* CheckPermission(const String& permissionString);
+ bool CanAccessGlobalVariable(const String& varName) override;
+ bool CanAccessConfigObject(const ConfigObject::Ptr& obj) override;
+
+private:
+ bool CheckPermissionAndEvalFilter(const String& permissionString, const Object::Ptr& obj, const String& varName);
+
+ std::unordered_map<String, std::pair<bool, std::unique_ptr<Expression>>> m_PermCache;
+ ApiUser::Ptr m_User;
+};
+
}
#endif /* FILTERUTILITY_H */
=====================================
lib/remote/jsonrpc.hpp
=====================================
@@ -26,8 +26,8 @@ public:
static size_t SendMessage(const Shared<AsioTlsStream>::Ptr& stream, const Dictionary::Ptr& message, boost::asio::yield_context yc);
static size_t SendRawMessage(const Shared<AsioTlsStream>::Ptr& stream, const String& json, boost::asio::yield_context yc);
- static String ReadMessage(const Shared<AsioTlsStream>::Ptr& stream, ssize_t maxMessageLength = -1);
- static String ReadMessage(const Shared<AsioTlsStream>::Ptr& stream, boost::asio::yield_context yc, ssize_t maxMessageLength = -1);
+ static String ReadMessage(const Shared<AsioTlsStream>::Ptr& stream, ssize_t maxMessageLength);
+ static String ReadMessage(const Shared<AsioTlsStream>::Ptr& stream, boost::asio::yield_context yc, ssize_t maxMessageLength);
static Dictionary::Ptr DecodeMessage(const String& message);
=====================================
lib/remote/jsonrpcconnection-pki.cpp
=====================================
@@ -226,13 +226,9 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
String realTicket = PBKDF2_SHA1(cn, salt, 50000);
- Log(LogDebug, "JsonRpcConnection")
- << "Certificate request for CN '" << cn << "': Comparing received ticket '"
- << ticket << "' with calculated ticket '" << realTicket << "'.";
-
if (!Utility::ComparePasswords(ticket, realTicket)) {
Log(LogWarning, "JsonRpcConnection")
- << "Ticket '" << ticket << "' for CN '" << cn << "' is invalid.";
+ << "Received ticket for CN '" << cn << "' is invalid.";
result->Set("status_code", 1);
result->Set("error", "Invalid ticket for CN '" + cn + "'.");
=====================================
lib/remote/jsonrpcconnection.cpp
=====================================
@@ -73,7 +73,10 @@ void JsonRpcConnection::HandleIncomingMessages(boost::asio::yield_context yc)
String jsonString;
try {
- jsonString = JsonRpc::ReadMessage(m_Stream, yc, m_Endpoint ? -1 : 1024 * 1024);
+ // Unauthenticated connections are limited to 1MB messages
+ jsonString = JsonRpc::ReadMessage(
+ m_Stream, yc, m_Endpoint ? m_Endpoint->GetMessageReceiveSizeLimit() : 1024L * 1024
+ );
} catch (const std::exception& ex) {
Log(m_ShuttingDown ? LogDebug : LogNotice, "JsonRpcConnection")
<< "Error while reading JSON-RPC message for identity '" << m_Identity
=====================================
lib/remote/pkiutility.cpp
=====================================
@@ -215,7 +215,8 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const
stream->flush();
for (;;) {
- response = JsonRpc::DecodeMessage(JsonRpc::ReadMessage(stream));
+ // Parent nodes are trusted to send messages of unbounded size
+ response = JsonRpc::DecodeMessage(JsonRpc::ReadMessage(stream, -1));
if (response && response->Contains("error")) {
Log(LogCritical, "cli", "Could not fetch valid response. Please check the master log (notice or debug).");
View it on GitLab: https://salsa.debian.org/nagios-team/icinga2/-/compare/ab28d6dc9dd4040f3b3bd819d495e5e13c557fb4...158318e252f0046a6e1c7c2b6437c01c12bbaac6
--
View it on GitLab: https://salsa.debian.org/nagios-team/icinga2/-/compare/ab28d6dc9dd4040f3b3bd819d495e5e13c557fb4...158318e252f0046a6e1c7c2b6437c01c12bbaac6
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-nagios-changes/attachments/20260818/0d412576/attachment-0001.htm>
More information about the pkg-nagios-changes
mailing list