From d183919ca31a0a537b7960dcd6babea99453dba3 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 17 Feb 2016 13:35:48 +0100 Subject: [PATCH 1/7] doc: Add syntax draft for restricting custom variables refs #10965 --- .../doc/restrict-custom-variables.md | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 modules/monitoring/doc/restrict-custom-variables.md diff --git a/modules/monitoring/doc/restrict-custom-variables.md b/modules/monitoring/doc/restrict-custom-variables.md new file mode 100644 index 000000000..e1c144472 --- /dev/null +++ b/modules/monitoring/doc/restrict-custom-variables.md @@ -0,0 +1,79 @@ +# Restrict Access to Custom Variables (WIP) + +* Restriction name: monitoring/blacklist/properties +* Restriction value: Comma separated list of GLOB like filters + +Imagine the following host custom variable structure. + +```` +host.vars. +|-- cmdb_name +|-- cmdb_id +|-- cmdb_location +|-- wiki_id +|-- passwords. +| |-- mysql_password +| |-- ldap_password +| `-- mongodb_password +|-- legacy. +| |-- cmdb_name +| |-- mysql_password +| `-- wiki_id +`-- backup. + `-- passwords. + |-- mysql_password + `-- ldap_password +```` + +`host.vars.cmdb_name` + +Blacklists cmdb_name in the first level of the custom variable structure only. +`host.vars.legacy.cmdb_name` is not blacklisted. + + +`host.vars.cmdb_*` + +All custom variables in the first level of the structure which begin with `cmdb_` become blacklisted. +Deeper custom variables are ignored. `host.vars.legacy.cmdb_name` is not blacklisted. + +`host.vars.*id` + +All custom variables in the first level of the structure which end with `id` become blacklisted. +Deeper custom variables are ignored. `host.vars.legacy.wiki_id` is not blacklisted. + +`host.vars.*.mysql_password` + +Matches all custom variables on the second level which are equal to `mysql_password`. + +`host.vars.*.*password` + +Matches all custom variables on the second level which end with `password`. + +`host.vars.*.{mysql_password,ldap_password}` + +Matches all custorm variables on the second level which equal `mysql_password` or `ldap_password`. + +`host.vars.**.*password` + +Matches all custom variables on all levels which end with `password`. + +Please note the two asterisks, `**`, here for crossing level boundaries. This syntax is used for matching the complete +custom variable structure. + +If you want to restrict all custom variables that end with password for both hosts and services, you have to define +the following restriction. + +`host.vars.**.*password,service.vars.**.*password` + +## Escape Meta Characters + +Use backslash to escape the meta characters + +* { +* } +* * +* , + +`host.vars.\*fall` + +Matches all custom variables in the first level which equal `*fall`. From 90cd481e84d70b2b47ad8389dfb073134741fa15 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 17 Feb 2016 15:26:24 +0100 Subject: [PATCH 2/7] doc: Use only three backticks for code blocks refs #10965 --- modules/monitoring/doc/restrict-custom-variables.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/monitoring/doc/restrict-custom-variables.md b/modules/monitoring/doc/restrict-custom-variables.md index e1c144472..849a25fd4 100644 --- a/modules/monitoring/doc/restrict-custom-variables.md +++ b/modules/monitoring/doc/restrict-custom-variables.md @@ -5,7 +5,7 @@ Imagine the following host custom variable structure. -```` +``` host.vars. |-- cmdb_name |-- cmdb_id @@ -23,7 +23,7 @@ host.vars. `-- passwords. |-- mysql_password `-- ldap_password -```` +``` `host.vars.cmdb_name` From 01e169b7569b5d8c65d1109a28266f18d48f3362 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Mon, 21 Mar 2016 14:49:37 +0100 Subject: [PATCH 3/7] doc: Remove curly brace expansion for custom var restriction refs #10965 --- modules/monitoring/doc/restrict-custom-variables.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/monitoring/doc/restrict-custom-variables.md b/modules/monitoring/doc/restrict-custom-variables.md index 849a25fd4..5416ecb76 100644 --- a/modules/monitoring/doc/restrict-custom-variables.md +++ b/modules/monitoring/doc/restrict-custom-variables.md @@ -49,7 +49,7 @@ Matches all custom variables on the second level which are equal to `mysql_passw Matches all custom variables on the second level which end with `password`. -`host.vars.*.{mysql_password,ldap_password}` +`host.vars.*.mysql_password,host.vars.*.ldap_password` Matches all custorm variables on the second level which equal `mysql_password` or `ldap_password`. @@ -69,8 +69,6 @@ the following restriction. Use backslash to escape the meta characters -* { -* } * * * , From 32b556c2b1f818f62cdf270fda6f8f942940a425 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Mar 2016 16:21:40 +0100 Subject: [PATCH 4/7] Provide restriction monitoring/blacklist/properties refs #10965 --- modules/monitoring/configuration.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 709bf5c80..01a6380ad 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -84,6 +84,10 @@ $this->provideRestriction( 'monitoring/filter/objects', $this->translate('Restrict views to the Icinga objects that match the filter') ); +$this->provideRestriction( + 'monitoring/blacklist/properties', + $this->translate('Hide the properties of monitored objects that match the filter') +); $this->provideConfigTab('backends', array( 'title' => $this->translate('Configure how to retrieve monitoring information'), From 66a7bdfc84f898e5b4eb3fd4765b70e963a2facb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Mar 2016 18:21:20 +0100 Subject: [PATCH 5/7] MonitoredObject: implement hideBlacklistedProperties() refs #10965 --- .../Monitoring/Object/MonitoredObject.php | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index ccd8f4b07..1f1ba7375 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -5,6 +5,7 @@ namespace Icinga\Module\Monitoring\Object; use stdClass; use InvalidArgumentException; +use Icinga\Authentication\Auth; use Icinga\Application\Config; use Icinga\Data\Filter\Filter; use Icinga\Data\Filterable; @@ -147,6 +148,13 @@ abstract class MonitoredObject implements Filterable */ protected $stats; + /** + * The properties to hide from the user + * + * @var array + */ + protected $blacklistedProperties = null; + /** * Create a monitored object, i.e. host or service * @@ -457,7 +465,9 @@ abstract class MonitoredObject implements Filterable $customvars = $this->hostVariables; } - $this->customvars = $this->obfuscateCustomVars($customvars, $blacklistPattern); + $this->customvars = $customvars; + $this->hideBlacklistedProperties(); + $this->customvars = $this->obfuscateCustomVars($this->customvars, $blacklistPattern); return $this; } @@ -485,6 +495,79 @@ abstract class MonitoredObject implements Filterable return $customvars instanceof stdClass ? (object) $obfuscatedCustomVars : $obfuscatedCustomVars; } + /** + * Hide all blacklisted properties from the user as restricted by monitoring/blacklist/properties + * + * Currently this only affects the custom variables + */ + protected function hideBlacklistedProperties() + { + if ($this->blacklistedProperties === null) { + $this->blacklistedProperties = array(); + foreach (Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') as $patterns) { + foreach (explode(',', $patterns) as $pattern) { + $pattern = explode('.', $pattern); + foreach ($pattern as & $subPattern) { + $subPattern = explode('*', $subPattern); + foreach ($subPattern as & $subPatternPart) { + if ($subPatternPart !== '') { + $subPatternPart = preg_quote($subPatternPart, '/'); + } + unset($subPatternPart); + } + $subPattern = '/^' . implode('.*', $subPattern) . '$/'; + unset($subPattern); + } + + $this->blacklistedProperties[] = $pattern; + } + } + } + + $allProperties = array($this->type => array('vars' => $this->customvars)); + foreach ($this->blacklistedProperties as $blacklistedProperty) { + $allProperties = $this->hideBlacklistedPropertiesRecursive($allProperties, $blacklistedProperty); + } + $this->customvars = $allProperties[$this->type]['vars']; + } + + /** + * Helper method for hideBlacklistedProperties() + * + * @param stdClass|array $allProperties + * @param array $blacklistedProperty + * + * @return stdClass|array + */ + protected function hideBlacklistedPropertiesRecursive($allProperties, $blacklistedProperty) + { + $isObject = $allProperties instanceof stdClass; + if ($isObject || is_array($allProperties)) { + if ($isObject) { + $allProperties = (array) $allProperties; + } + + $currentLevel = $blacklistedProperty[0]; + $nextLevels = count($blacklistedProperty) === 1 ? null : array_slice($blacklistedProperty, 1); + foreach ($allProperties as $k => & $v) { + if (preg_match($currentLevel, (string) $k)) { + if ($nextLevels === null) { + unset($allProperties[$k]); + } else { + $v = $this->hideBlacklistedPropertiesRecursive($v, $nextLevels); + } + } + unset($v); + } + + if ($isObject) { + $allProperties = (object) $allProperties; + } + } + + return $allProperties; + } + /** * Fetch the host custom variables related to this object * From 589da9bcd15fb9570bed21aafdae9b9beab9797e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 23 Mar 2016 13:00:53 +0100 Subject: [PATCH 6/7] monitoring: Apply custom variable restrictions refs #10965 --- library/Icinga/Util/GlobFilter.php | 180 ++++++++++ .../Monitoring/Object/MonitoredObject.php | 71 +--- .../library/Icinga/Util/GlobFilterTest.php | 325 ++++++++++++++++++ 3 files changed, 514 insertions(+), 62 deletions(-) create mode 100644 library/Icinga/Util/GlobFilter.php create mode 100644 test/php/library/Icinga/Util/GlobFilterTest.php diff --git a/library/Icinga/Util/GlobFilter.php b/library/Icinga/Util/GlobFilter.php new file mode 100644 index 000000000..f0dde9f97 --- /dev/null +++ b/library/Icinga/Util/GlobFilter.php @@ -0,0 +1,180 @@ + array( + * 'bar' => array( + * 'baz' => 'deadbeef' // <--- + * ) + * ) + * ) + */ +class GlobFilter +{ + /** + * The prepared filters + * + * @var array + */ + protected $filters; + + /** + * Create a new filter from a comma-separated list of GLOB-like filters or an array of such lists. + * + * @param string|\Traversable $filters + */ + public function __construct($filters) + { + $patterns = array(array('')); + $lastIndex1 = $lastIndex2 = 0; + + foreach ((is_string($filters) ? array($filters) : $filters) as $rawPatterns) { + $escape = false; + + foreach (str_split($rawPatterns) as $c) { + if ($escape) { + $escape = false; + $patterns[$lastIndex1][$lastIndex2] .= preg_quote($c, '/'); + } else { + switch ($c) { + case '\\': + $escape = true; + break; + case ',': + $patterns[] = array(''); + ++$lastIndex1; + $lastIndex2 = 0; + break; + case '.': + $patterns[$lastIndex1][] = ''; + ++$lastIndex2; + break; + case '*': + $patterns[$lastIndex1][$lastIndex2] .= '.*'; + break; + default: + $patterns[$lastIndex1][$lastIndex2] .= preg_quote($c, '/'); + } + } + } + + if ($escape) { + $patterns[$lastIndex1][$lastIndex2] .= '\\\\'; + } + } + + $this->filters = array(); + + foreach ($patterns as $pattern) { + foreach ($pattern as $i => $subPattern) { + if ($subPattern === '') { + unset($pattern[$i]); + } elseif ($subPattern === '.*.*') { + $pattern[$i] = '**'; + } else { + $pattern[$i] = '/^' . $subPattern . '$/'; + } + } + + if (! empty($pattern)) { + $found = false; + foreach ($pattern as $i => $v) { + if ($found) { + if ($v === '**') { + unset($pattern[$i]); + } else { + $found = false; + } + } elseif ($v === '**') { + $found = true; + } + } + + if (end($pattern) === '**') { + $pattern[] = '/^.*$/'; + } + + $this->filters[] = array_values($pattern); + } + } + } + + /** + * Remove all keys/attributes matching any of $this->filters from $dataStructure + * + * @param stdClass|array $dataStructure + * + * @return stdClass|array The modified copy of $dataStructure + */ + public function removeMatching($dataStructure) + { + foreach ($this->filters as $filter) { + $dataStructure = static::removeMatchingRecursive($dataStructure, $filter); + } + return $dataStructure; + } + + /** + * Helper method for removeMatching() + * + * @param stdClass|array $dataStructure + * @param array $filter + * + * @return stdClass|array + */ + protected static function removeMatchingRecursive($dataStructure, $filter) + { + $multiLevelPattern = $filter[0] === '**'; + if ($multiLevelPattern) { + $dataStructure = static::removeMatchingRecursive($dataStructure, array_slice($filter, 1)); + } + + $isObject = $dataStructure instanceof stdClass; + if ($isObject || is_array($dataStructure)) { + if ($isObject) { + $dataStructure = (array) $dataStructure; + } + + if ($multiLevelPattern) { + foreach ($dataStructure as $k => & $v) { + $v = static::removeMatchingRecursive($v, $filter); + unset($v); + } + } else { + $currentLevel = $filter[0]; + $nextLevels = count($filter) === 1 ? null : array_slice($filter, 1); + foreach ($dataStructure as $k => & $v) { + if (preg_match($currentLevel, (string) $k)) { + if ($nextLevels === null) { + unset($dataStructure[$k]); + } else { + $v = static::removeMatchingRecursive($v, $nextLevels); + } + } + unset($v); + } + } + + if ($isObject) { + $dataStructure = (object) $dataStructure; + } + } + + return $dataStructure; + } +} diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php index 1f1ba7375..437268eb8 100644 --- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php +++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php @@ -12,6 +12,7 @@ use Icinga\Data\Filterable; use Icinga\Exception\InvalidPropertyException; use Icinga\Exception\ProgrammingError; use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Util\GlobFilter; use Icinga\Web\UrlParams; /** @@ -151,7 +152,7 @@ abstract class MonitoredObject implements Filterable /** * The properties to hide from the user * - * @var array + * @var GlobFilter */ protected $blacklistedProperties = null; @@ -503,69 +504,15 @@ abstract class MonitoredObject implements Filterable protected function hideBlacklistedProperties() { if ($this->blacklistedProperties === null) { - $this->blacklistedProperties = array(); - foreach (Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') as $patterns) { - foreach (explode(',', $patterns) as $pattern) { - $pattern = explode('.', $pattern); - foreach ($pattern as & $subPattern) { - $subPattern = explode('*', $subPattern); - foreach ($subPattern as & $subPatternPart) { - if ($subPatternPart !== '') { - $subPatternPart = preg_quote($subPatternPart, '/'); - } - unset($subPatternPart); - } - $subPattern = '/^' . implode('.*', $subPattern) . '$/'; - unset($subPattern); - } - - $this->blacklistedProperties[] = $pattern; - } - } + $this->blacklistedProperties = new GlobFilter( + Auth::getInstance()->getRestrictions('monitoring/blacklist/properties') + ); } - $allProperties = array($this->type => array('vars' => $this->customvars)); - foreach ($this->blacklistedProperties as $blacklistedProperty) { - $allProperties = $this->hideBlacklistedPropertiesRecursive($allProperties, $blacklistedProperty); - } - $this->customvars = $allProperties[$this->type]['vars']; - } - - /** - * Helper method for hideBlacklistedProperties() - * - * @param stdClass|array $allProperties - * @param array $blacklistedProperty - * - * @return stdClass|array - */ - protected function hideBlacklistedPropertiesRecursive($allProperties, $blacklistedProperty) - { - $isObject = $allProperties instanceof stdClass; - if ($isObject || is_array($allProperties)) { - if ($isObject) { - $allProperties = (array) $allProperties; - } - - $currentLevel = $blacklistedProperty[0]; - $nextLevels = count($blacklistedProperty) === 1 ? null : array_slice($blacklistedProperty, 1); - foreach ($allProperties as $k => & $v) { - if (preg_match($currentLevel, (string) $k)) { - if ($nextLevels === null) { - unset($allProperties[$k]); - } else { - $v = $this->hideBlacklistedPropertiesRecursive($v, $nextLevels); - } - } - unset($v); - } - - if ($isObject) { - $allProperties = (object) $allProperties; - } - } - - return $allProperties; + $allProperties = $this->blacklistedProperties->removeMatching( + array($this->type => array('vars' => $this->customvars)) + ); + $this->customvars = isset($allProperties[$this->type]['vars']) ? $allProperties[$this->type]['vars'] : array(); } /** diff --git a/test/php/library/Icinga/Util/GlobFilterTest.php b/test/php/library/Icinga/Util/GlobFilterTest.php new file mode 100644 index 000000000..2ec0e101f --- /dev/null +++ b/test/php/library/Icinga/Util/GlobFilterTest.php @@ -0,0 +1,325 @@ +assertTrue( + $filter->removeMatching($unfiltered) === $filtered, + 'Filter `' . $filterPattern . '\' doesn\'t work as intended' + ); + } + + public function testPatternWithoutAnyWildcards() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.cmdb_name', + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'cmdb_id' => '', + 'legacy' => array( + 'cmdb_name' => '' + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'cmdb_id' => '', + 'legacy' => array( + 'cmdb_name' => '' + ) + ) + ) + ) + ); + } + + public function testPatternWithAnAsteriskAtTheEndOfAComponent() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.cmdb_*', + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'cmdb_id' => '', + 'cmdb_location' => '', + 'wiki_id' => '', + 'legacy' => array( + 'cmdb_name' => '' + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'wiki_id' => '', + 'legacy' => array( + 'cmdb_name' => '' + ) + ) + ) + ) + ); + } + + public function testPatternWithAnAsteriskAtTheBeginningOfAComponent() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.*id', + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'cmdb_id' => '', + 'wiki_id' => '', + 'legacy' => array( + 'wiki_id' => '' + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'legacy' => array( + 'wiki_id' => '' + ) + ) + ) + ) + ); + } + + public function testPatternWithAComponentBeingTheAsteriskOnly() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.*.mysql_password', + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '' + ), + 'legacy' => array( + 'mysql_password' => '' + ), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '' + ) + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'passwords' => array( + 'ldap_password' => '' + ), + 'legacy' => array(), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '' + ) + ) + ) + ) + ) + ); + } + + public function testPatternWithTwoComponentsContainingAsterisks() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.*.*password', + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '', + 'mongodb_password' => '' + ), + 'legacy' => array( + 'cmdb_name' => '', + 'mysql_password' => '' + ), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '' + ) + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'passwords' => array(), + 'legacy' => array( + 'cmdb_name' => '' + ), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '' + ) + ) + ) + ) + ) + ); + } + + public function testTwoCommaSeparatedPatternsEachWithAnAsterisk() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.*.mysql_password,host.vars.*.ldap_password', + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '', + 'mongodb_password' => '' + ), + 'legacy' => array( + 'mysql_password' => '' + ), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '' + ) + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'cmdb_name' => '', + 'passwords' => array( + 'mongodb_password' => '' + ), + 'legacy' => array(), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '' + ) + ) + ) + ) + ) + ); + } + + public function testPatternWithAComponentBeingTheMultiLevelWildcard() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.**.*password', + array( + 'host' => array( + 'vars' => array( + 'cmdb_location' => '', + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '', + 'mongodb_password' => '' + ), + 'legacy' => array( + 'mysql_password' => '', + ), + 'backup' => array( + 'passwords' => array( + 'mysql_password' => '', + 'ldap_password' => '' + ) + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'cmdb_location' => '', + 'passwords' => array(), + 'legacy' => array(), + 'backup' => array( + 'passwords' => array() + ) + ) + ) + ) + ); + } + + public function testPatternWithAnEscapedAsterisk() + { + $this->assertGlobFilterRemovesMatching( + 'host.vars.**.\*password', + array( + 'host' => array( + 'vars' => array( + 'wiki_id' => '', + 'passwords' => array( + 'mongodb_password' => '', + '*password' => '' + ), + 'legacy' => array( + 'mysql_password' => '', + '*password' => '' + ), + 'backup' => array( + 'passwords' => array( + '*password' => '', + 'ldap_password' => '' + ) + ) + ) + ) + ), + array( + 'host' => array( + 'vars' => array( + 'wiki_id' => '', + 'passwords' => array( + 'mongodb_password' => '' + ), + 'legacy' => array( + 'mysql_password' => '' + ), + 'backup' => array( + 'passwords' => array( + 'ldap_password' => '' + ) + ) + ) + ) + ) + ); + } +} From b0a6eb5b1ecd960c715678a201790de689eff3b6 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 13 Apr 2016 15:54:41 +0200 Subject: [PATCH 7/7] Ignore case and whitespaces in the glob filter refs #10965 --- library/Icinga/Util/GlobFilter.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/Icinga/Util/GlobFilter.php b/library/Icinga/Util/GlobFilter.php index f0dde9f97..15cbe0b99 100644 --- a/library/Icinga/Util/GlobFilter.php +++ b/library/Icinga/Util/GlobFilter.php @@ -86,8 +86,10 @@ class GlobFilter unset($pattern[$i]); } elseif ($subPattern === '.*.*') { $pattern[$i] = '**'; - } else { + } elseif ($subPattern === '.*') { $pattern[$i] = '/^' . $subPattern . '$/'; + } else { + $pattern[$i] = '/^' . trim($subPattern) . '$/i'; } }