mirror of
https://github.com/Icinga/icinga2.git
synced 2026-07-07 16:10:56 -04:00
Add filter-expression permission
This allows preventing ApiUsers from evaluating their own DSL expressions for improved security.
This commit is contained in:
parent
91eeb41ff1
commit
324d5bb815
19 changed files with 217 additions and 25 deletions
|
|
@ -363,6 +363,15 @@ Run the following command to:
|
|||
icinga2 api setup
|
||||
```
|
||||
|
||||
For new installations, it is recommended to set the following additional attribute inside the `ApiListener` object
|
||||
definition in `/etc/icinga2/features-enabled/api.conf`. This will already enforce stricter permissions as they will
|
||||
become the default with v2.17 (see the [upgrading documentation](16-upgrading-icinga-2.md#upgrading-to-2-16-2) for the
|
||||
version that introduced that setting for more details):
|
||||
|
||||
```
|
||||
enforce_filter_expression_permission = true
|
||||
```
|
||||
|
||||
Restart Icinga 2 for these changes to take effect.
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1088,28 +1088,29 @@ object ApiListener "api" {
|
|||
|
||||
Configuration Attributes:
|
||||
|
||||
Name | Type | Description
|
||||
--------------------------------------|-----------------------|----------------------------------
|
||||
cert\_path | String | **Deprecated.** Path to the public key.
|
||||
key\_path | String | **Deprecated.** Path to the private key.
|
||||
ca\_path | String | **Deprecated.** Path to the CA certificate file.
|
||||
ticket\_salt | String | **Optional.** Private key for [CSR auto-signing](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing). **Required** for a signing master instance.
|
||||
crl\_path | String | **Optional.** Path to the CRL file.
|
||||
bind\_host | String | **Optional.** The IP address the api listener should be bound to. If not specified, the ApiListener is bound to `::` and listens for both IPv4 and IPv6 connections or to `0.0.0.0` if IPv6 is not supported by the operating system.
|
||||
bind\_port | Number | **Optional.** The port the api listener should be bound to. Defaults to `5665`.
|
||||
accept\_config | Boolean | **Optional.** Accept zone configuration. Defaults to `false`.
|
||||
accept\_commands | Boolean | **Optional.** Accept remote commands. Defaults to `false`.
|
||||
max\_anonymous\_clients | Number | **Optional.** Limit the number of anonymous client connections (not configured endpoints and signing requests).
|
||||
cipher\_list | String | **Optional.** Cipher list that is allowed. For a list of available ciphers run `openssl ciphers`. Defaults to `ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256`.
|
||||
tls\_protocolmin | String | **Optional.** Minimum TLS protocol version. Since v2.11, only `TLSv1.2` is supported. Defaults to `TLSv1.2`.
|
||||
tls\_handshake\_timeout | Number | **Deprecated.** TLS Handshake timeout. Defaults to `10s`.
|
||||
connect\_timeout | Number | **Optional.** Timeout for establishing new connections. Affects both incoming and outgoing connections. Within this time, the TCP and TLS handshakes must complete and either a HTTP request or an Icinga cluster connection must be initiated. Defaults to `15s`.
|
||||
access\_control\_allow\_origin | Array | **Optional.** Specifies an array of origin URLs that may access the API. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin)
|
||||
access\_control\_allow\_credentials | Boolean | **Deprecated.** Indicates whether or not the actual request can be made using credentials. Defaults to `true`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials)
|
||||
access\_control\_allow\_headers | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers)
|
||||
access\_control\_allow\_methods | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods)
|
||||
environment | String | **Optional.** Used as suffix in TLS SNI extension name; default from constant `ApiEnvironment`, which is empty.
|
||||
http\_response\_headers | Dictionary | **Optional.** Additional headers to add to HTTP responses, for example `{"Strict-Transport-Security" = "max-age=31536000"}`. Defaults to none.
|
||||
Name | Type | Description
|
||||
----------------------------------------|------------|----------------------------------
|
||||
cert\_path | String | **Deprecated.** Path to the public key.
|
||||
key\_path | String | **Deprecated.** Path to the private key.
|
||||
ca\_path | String | **Deprecated.** Path to the CA certificate file.
|
||||
ticket\_salt | String | **Optional.** Private key for [CSR auto-signing](06-distributed-monitoring.md#distributed-monitoring-setup-csr-auto-signing). **Required** for a signing master instance.
|
||||
crl\_path | String | **Optional.** Path to the CRL file.
|
||||
bind\_host | String | **Optional.** The IP address the api listener should be bound to. If not specified, the ApiListener is bound to `::` and listens for both IPv4 and IPv6 connections or to `0.0.0.0` if IPv6 is not supported by the operating system.
|
||||
bind\_port | Number | **Optional.** The port the api listener should be bound to. Defaults to `5665`.
|
||||
accept\_config | Boolean | **Optional.** Accept zone configuration. Defaults to `false`.
|
||||
accept\_commands | Boolean | **Optional.** Accept remote commands. Defaults to `false`.
|
||||
max\_anonymous\_clients | Number | **Optional.** Limit the number of anonymous client connections (not configured endpoints and signing requests).
|
||||
cipher\_list | String | **Optional.** Cipher list that is allowed. For a list of available ciphers run `openssl ciphers`. Defaults to `ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256`.
|
||||
tls\_protocolmin | String | **Optional.** Minimum TLS protocol version. Since v2.11, only `TLSv1.2` is supported. Defaults to `TLSv1.2`.
|
||||
tls\_handshake\_timeout | Number | **Deprecated.** TLS Handshake timeout. Defaults to `10s`.
|
||||
connect\_timeout | Number | **Optional.** Timeout for establishing new connections. Affects both incoming and outgoing connections. Within this time, the TCP and TLS handshakes must complete and either a HTTP request or an Icinga cluster connection must be initiated. Defaults to `15s`.
|
||||
access\_control\_allow\_origin | Array | **Optional.** Specifies an array of origin URLs that may access the API. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin)
|
||||
access\_control\_allow\_credentials | Boolean | **Deprecated.** Indicates whether or not the actual request can be made using credentials. Defaults to `true`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials)
|
||||
access\_control\_allow\_headers | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers)
|
||||
access\_control\_allow\_methods | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods)
|
||||
environment | String | **Optional.** Used as suffix in TLS SNI extension name; default from constant `ApiEnvironment`, which is empty.
|
||||
http\_response\_headers | Dictionary | **Optional.** Additional headers to add to HTTP responses, for example `{"Strict-Transport-Security" = "max-age=31536000"}`. Defaults to none.
|
||||
enforce\_filter\_expression\_permission | Boolean | **Optional.** Enforce the `filter-expression` permission. Defaults to `false` until v2.17 for compatibility.
|
||||
|
||||
The attributes `access_control_allow_credentials`, `access_control_allow_headers` and `access_control_allow_methods`
|
||||
are controlled by Icinga 2 and are not changeable by config any more.
|
||||
|
|
|
|||
|
|
@ -299,6 +299,18 @@ Available permissions for specific URL endpoints:
|
|||
types | /v1/types | Yes | 1
|
||||
variables | /v1/variables | Yes | 1
|
||||
|
||||
Available permissions that are not bound to specific URL endpoints:
|
||||
|
||||
Permissions | Description
|
||||
------------------------------|------------
|
||||
filter-expression | Allows the user to provide their own [advanced filter expressions](12-icinga2-api.md#icinga2-api-advanced-filters).
|
||||
|
||||
!!! warning
|
||||
|
||||
The `filter-expression` permission was introduced in v2.16.2 and is only enforced if the
|
||||
[`enforce_filter_expression_permission` attribute of `ApiListener`](09-object-types.md#objecttype-apilistener)
|
||||
is set to `true`. For compatibility reasons, this will not be enforced by default until v2.17.
|
||||
|
||||
The required actions or types can be replaced by using a wildcard match ("\*").
|
||||
|
||||
|
||||
|
|
@ -435,6 +447,16 @@ The syntax for these filters is the same like for [apply rule expressions](03-mo
|
|||
The `filter` parameter can only be specified once, complex filters must
|
||||
be defined once in the provided string value.
|
||||
|
||||
!!! warning
|
||||
|
||||
In order to use these advanced filters, the `ApiUser` must be granted the `filter-expression` permission,
|
||||
which should only be done for trusted users. The evaluation happens in the main Icinga 2 worker process and may be
|
||||
abused for denial-of-service attacks, potentially crashing the Icinga 2 daemon.
|
||||
|
||||
Note that before v2.17, this permission is not enforced by default but only if the
|
||||
[`enforce_filter_expression_permission` attribute of `ApiListener`](09-object-types.md#objecttype-apilistener)
|
||||
is set accordingly.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Filters used as URL parameter must be URL-encoded. The following examples
|
||||
|
|
|
|||
|
|
@ -8,6 +8,23 @@ Specific version upgrades are described below. Please note that version
|
|||
updates are incremental. An upgrade from v2.6 to v2.8 requires to
|
||||
follow the instructions for v2.7 too.
|
||||
|
||||
## Upgrading to v2.16.2, v2.15.4, or v2.14.9 <a id="upgrading-to-2-16-2"></a>
|
||||
|
||||
### New `filter-expression` permission
|
||||
|
||||
When using the Icinga 2 REST API, filter expressions are a powerful tool. However, that power can also be abused for
|
||||
denial-of-service attacks by authenticated users. Given that these filter expressions are Icinga 2 DSL expressions and
|
||||
their evaluation happens in the main Icinga 2 worker process, this may also crash the Icinga 2 daemon.
|
||||
|
||||
In order to allow mitigating such problems, a new permission named `filter-expression` was added. **Before v2.17, this
|
||||
permission will not be enforced by default** due to the impact on existing installations. Nonetheless, we recommend
|
||||
reviewing your `ApiUser` configuration, explicitly allowing the new permission where necessary and enabling the
|
||||
enforcement of the permission using the [`enforce_filter_expression_permission` attribute of
|
||||
`ApiListener`](09-object-types.md#objecttype-apilistener). If an `ApiUser` makes use of the permission while the
|
||||
permission is not enforced yet, it will be logged. Thus, it is also possible to upgrade, observe the logs, grant the
|
||||
permission as needed or adapting API clients to avoid using filters if possible, and enable the enforcement at a later
|
||||
time.
|
||||
|
||||
## Upgrading to v2.16 <a id="upgrading-to-2-16"></a>
|
||||
|
||||
### Migrating from ElasticsearchWriter to OTLPMetricsWriter
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ bool ActionsHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user);
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No objects found.",
|
||||
|
|
|
|||
|
|
@ -234,6 +234,18 @@ void ApiListener::OnAllConfigLoaded()
|
|||
|
||||
if (!m_LocalEndpoint)
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Endpoint object for '" + GetIdentity() + "' is missing.", GetDebugInfo()));
|
||||
|
||||
if (!GetEnforceFilterExpressionPermission()) {
|
||||
Log(LogWarning, "ApiListener") << "Security notice:\n"
|
||||
" Currently, all ApiUsers are allowed to use Icinga 2 DSL filter expressions\n"
|
||||
" in API queries because enforce_filter_expression_permission is set to false.\n"
|
||||
" This can pose a security risk as filters are evaluated within the Icinga 2\n"
|
||||
" process and their complexity can be used for denial of service attacks. The\n"
|
||||
" new '" << ApiUser::FilterExpressionPerm << "' permission can be used to allow this for individual\n"
|
||||
" ApiUsers, which should only be granted to trusted users. It is recommended\n"
|
||||
" to set enforce_filter_expression_permission to true to enforce the\n"
|
||||
" permission. This will become the default in v2.17.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ class ApiListener : ConfigObject
|
|||
[no_user_modify] String identity;
|
||||
|
||||
[state, no_user_modify] Dictionary::Ptr last_failed_zones_stage_validation;
|
||||
|
||||
[config, no_user_modify] bool enforce_filter_expression_permission {
|
||||
default {{{ return false; }}}
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ public:
|
|||
|
||||
static ApiUser::Ptr GetByClientCN(const String& cn);
|
||||
static ApiUser::Ptr GetByAuthHeader(const String& auth_header);
|
||||
|
||||
static inline const String FilterExpressionPerm = "filter-expression";
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ bool DeleteObjectHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user);
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No objects found.",
|
||||
|
|
|
|||
|
|
@ -92,7 +92,18 @@ bool EventsHandler::HandleRequest(
|
|||
}
|
||||
}
|
||||
|
||||
EventsSubscriber subscriber (std::move(eventTypes), HttpUtility::GetLastParameter(params, "filter"), l_ApiQuery);
|
||||
String filter = "";
|
||||
if (params && params->Contains("filter")) {
|
||||
if (!FilterUtility::HasPermission(request.User(), ApiUser::FilterExpressionPerm, nullptr)) {
|
||||
HttpUtility::SendJsonError(response, params, 403,
|
||||
"Missing permission: " + ApiUser::FilterExpressionPerm);
|
||||
return true;
|
||||
}
|
||||
|
||||
filter = HttpUtility::GetLastParameter(params, "filter");
|
||||
}
|
||||
|
||||
EventsSubscriber subscriber (std::move(eventTypes), std::move(filter), l_ApiQuery);
|
||||
|
||||
response.result(http::status::ok);
|
||||
response.set(http::field::content_type, "application/json");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "remote/filterutility.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/httputility.hpp"
|
||||
#include "config/applyrule.hpp"
|
||||
#include "config/configcompiler.hpp"
|
||||
|
|
@ -300,6 +301,22 @@ bool FilterUtility::HasPermission(const ApiUser::Ptr& user, const String& permis
|
|||
}
|
||||
}
|
||||
|
||||
// Requiring the "filter-expression" permission to use any filter expression is an incompatible change. Therefore,
|
||||
// there is a config option to configure whether that permission check is enforced or not. If it is not enforced,
|
||||
// a message is logged, allowing the admin to determine which ApiUsers require this permission. This allows granting
|
||||
// the permission and/or adapting clients as needed until this message does not show up anymore and then switching
|
||||
// to enforcing the permission check.
|
||||
if (!foundPermission && permission == ApiUser::FilterExpressionPerm) {
|
||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||
if (listener && !listener->GetEnforceFilterExpressionPermission()) {
|
||||
Log(LogWarning, "FilterUtility") << "ApiUser '" << user->GetName()
|
||||
<< "' was allowed to use a filter expression despite missing the '" << ApiUser::FilterExpressionPerm
|
||||
<< "' permission due to ApiListener.enforce_filter_expression_permission = false.";
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPermission) {
|
||||
Log(LogWarning, "FilterUtility")
|
||||
<< "Missing permission: " << requiredPermission;
|
||||
|
|
@ -311,7 +328,7 @@ bool FilterUtility::HasPermission(const ApiUser::Ptr& user, const String& permis
|
|||
void FilterUtility::CheckPermission(const ApiUser::Ptr& user, const String& permission, std::unique_ptr<Expression>* permissionFilter)
|
||||
{
|
||||
if (!HasPermission(user, permission, permissionFilter)) {
|
||||
BOOST_THROW_EXCEPTION(ScriptError("Missing permission: " + permission.ToLower()));
|
||||
BOOST_THROW_EXCEPTION(MissingPermissionError("Missing permission: " + permission.ToLower()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,6 +403,7 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
|
|||
frame.PermChecker = permissionChecker;
|
||||
|
||||
if (query->Contains("filter")) {
|
||||
CheckPermission(user, ApiUser::FilterExpressionPerm, nullptr);
|
||||
String filter = HttpUtility::GetLastParameter(query, "filter");
|
||||
std::unique_ptr<Expression> ufilter = ConfigCompiler::CompileText("<API query>", filter);
|
||||
Dictionary::Ptr filter_vars = query->Get("filter_vars");
|
||||
|
|
|
|||
|
|
@ -62,6 +62,19 @@ public:
|
|||
const Object::Ptr& target, const String& variableName = String());
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to report a missing permission to an API user.
|
||||
*
|
||||
* IMPORTANT: The what() message is reported back to the user and MUST NOT contain sensitive information like names of
|
||||
* objects they are not allowed to access. When using the exception, also pay attention that throwing the exception
|
||||
* does not introduce a sidechannel. For example, it should not be returned if a user-specified object exists but the
|
||||
* user is not allowed to access it, otherwise they would learn that the object exists.
|
||||
*/
|
||||
class MissingPermissionError : public ScriptError
|
||||
{
|
||||
using ScriptError::ScriptError;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* FILTERUTILITY_H */
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ bool ModifyObjectHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user);
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No objects found.",
|
||||
|
|
|
|||
|
|
@ -178,6 +178,9 @@ bool ObjectQueryHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user);
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No objects found.",
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@ bool StatusHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user);
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No objects found.",
|
||||
|
|
|
|||
|
|
@ -119,6 +119,9 @@ bool TemplateQueryHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user, "tmpl");
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No templates found.",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ bool TypeQueryHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user);
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No objects found.",
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ bool VariableQueryHandler::HandleRequest(
|
|||
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, params, user, "variable");
|
||||
} catch (const MissingPermissionError& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 403, ex.what());
|
||||
return true;
|
||||
} catch (const std::exception& ex) {
|
||||
HttpUtility::SendJsonError(response, params, 404,
|
||||
"No variables found.",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,60 @@ BOOST_AUTO_TEST_SUITE(remote_filterutility,
|
|||
*boost::unit_test::label("config"))
|
||||
// clang-format on
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(filter_expression_permission, IcingaApplicationFixture)
|
||||
{
|
||||
auto createObjects = []() {
|
||||
String config = R"CONFIG({
|
||||
object CheckCommand "dummy" {
|
||||
command = "/bin/echo"
|
||||
}
|
||||
|
||||
object ApiUser "withFilterPermission" {
|
||||
permissions = [ "objects/query/*", "filter-expression" ]
|
||||
}
|
||||
|
||||
object ApiUser "withoutFilterPermission" {
|
||||
permissions = [ "objects/query/*" ]
|
||||
}
|
||||
|
||||
object Host "host1" {
|
||||
address = "host1"
|
||||
check_command = "dummy"
|
||||
}
|
||||
})CONFIG";
|
||||
std::unique_ptr<Expression> expr = ConfigCompiler::CompileText("<test>", config);
|
||||
expr->Evaluate(*ScriptFrame::GetCurrentFrame());
|
||||
};
|
||||
|
||||
ConfigItem::RunWithActivationContext(new Function("CreateTestObjects", createObjects));
|
||||
|
||||
auto userWithPerm = ApiUser::GetByName("withFilterPermission");
|
||||
auto userWithoutPerm = ApiUser::GetByName("withoutFilterPermission");
|
||||
|
||||
QueryDescription qd;
|
||||
qd.Types.insert("Host");
|
||||
qd.Permission = "objects/query/Host";
|
||||
|
||||
Dictionary::Ptr queryParams = new Dictionary();
|
||||
queryParams->Set("type", "Host");
|
||||
|
||||
// This is a filter that uses a get_object call on an object the permissionFilterUser
|
||||
// has access to. A second user is tested that has access to everything, to make sure
|
||||
// the filter evaluates properly in the first place.
|
||||
queryParams->Set("filter", "true");
|
||||
|
||||
std::vector<Value> objs;
|
||||
BOOST_REQUIRE_NO_THROW(objs = FilterUtility::GetFilterTargets(qd, queryParams, userWithPerm));
|
||||
BOOST_CHECK_EQUAL(objs.size(), 1);
|
||||
|
||||
BOOST_CHECK_EXCEPTION(FilterUtility::GetFilterTargets(qd, queryParams, userWithoutPerm), std::exception,
|
||||
[](const std::exception& ex) {
|
||||
boost::test_tools::assertion_result result{std::string_view(ex.what()) == "Missing permission: filter-expression"};
|
||||
result.message() << "got exception: " << ex.what();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(safe_function_permissions, IcingaApplicationFixture)
|
||||
{
|
||||
auto createObjects = []() {
|
||||
|
|
@ -29,6 +83,7 @@ object ApiUser "allPermissionsUser" {
|
|||
|
||||
object ApiUser "permissionFilterUser" {
|
||||
permissions = [
|
||||
"filter-expression",
|
||||
{
|
||||
permission = "objects/query/Host"
|
||||
filter = {{ host.name == {{{host1}}} }}
|
||||
|
|
@ -161,6 +216,7 @@ object ApiUser "allPermissionsUser" {
|
|||
|
||||
object ApiUser "permissionFilterUser" {
|
||||
permissions = [
|
||||
"filter-expression",
|
||||
"objects/query/Host",
|
||||
{
|
||||
permission = "variables"
|
||||
|
|
@ -170,7 +226,10 @@ object ApiUser "permissionFilterUser" {
|
|||
}
|
||||
|
||||
object ApiUser "noVariablePermUser" {
|
||||
permissions = [ "objects/query/Host" ]
|
||||
permissions = [
|
||||
"filter-expression",
|
||||
"objects/query/Host",
|
||||
]
|
||||
}
|
||||
|
||||
object Host "host1" {
|
||||
|
|
|
|||
Loading…
Reference in a new issue