commit
118055c07d
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Util;
|
||||
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* GLOB-like filter for simple data structures
|
||||
*
|
||||
* e.g. this filters:
|
||||
*
|
||||
* foo.bar.baz
|
||||
* foo.b*r.baz
|
||||
* **.baz
|
||||
*
|
||||
* match this one:
|
||||
*
|
||||
* array(
|
||||
* 'foo' => 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] = '**';
|
||||
} elseif ($subPattern === '.*') {
|
||||
$pattern[$i] = '/^' . $subPattern . '$/';
|
||||
} else {
|
||||
$pattern[$i] = '/^' . trim($subPattern) . '$/i';
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
# 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,host.vars.*.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`.
|
|
@ -5,12 +5,14 @@ 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;
|
||||
use Icinga\Exception\InvalidPropertyException;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Util\GlobFilter;
|
||||
use Icinga\Web\UrlParams;
|
||||
|
||||
/**
|
||||
|
@ -147,6 +149,13 @@ abstract class MonitoredObject implements Filterable
|
|||
*/
|
||||
protected $stats;
|
||||
|
||||
/**
|
||||
* The properties to hide from the user
|
||||
*
|
||||
* @var GlobFilter
|
||||
*/
|
||||
protected $blacklistedProperties = null;
|
||||
|
||||
/**
|
||||
* Create a monitored object, i.e. host or service
|
||||
*
|
||||
|
@ -457,7 +466,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 +496,25 @@ 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 = new GlobFilter(
|
||||
Auth::getInstance()->getRestrictions('monitoring/blacklist/properties')
|
||||
);
|
||||
}
|
||||
|
||||
$allProperties = $this->blacklistedProperties->removeMatching(
|
||||
array($this->type => array('vars' => $this->customvars))
|
||||
);
|
||||
$this->customvars = isset($allProperties[$this->type]['vars']) ? $allProperties[$this->type]['vars'] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the host custom variables related to this object
|
||||
*
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Tests\Icinga\Util;
|
||||
|
||||
use Icinga\Util\GlobFilter;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
|
||||
class GlobFilterTest extends BaseTestCase
|
||||
{
|
||||
protected function assertGlobFilterRemovesMatching($filterPattern, $unfiltered, $filtered)
|
||||
{
|
||||
$filter = new GlobFilter($filterPattern);
|
||||
$this->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' => ''
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue