diff --git a/AUTHORS b/AUTHORS index 8a0bc91d0..f56d6e24d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ Marius Hein Markus Frosch Matthias Jentsch Michael Friedrich +Paul Richards Rene Moser Susanne Vestner-Ludwig Sylph Lin diff --git a/application/forms/Config/Resource/SshResourceForm.php b/application/forms/Config/Resource/SshResourceForm.php new file mode 100644 index 000000000..c5c49d1e0 --- /dev/null +++ b/application/forms/Config/Resource/SshResourceForm.php @@ -0,0 +1,147 @@ +setName('form_config_resource_ssh'); + } + + /** + * @see Form::createElements() + */ + public function createElements(array $formData) + { + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Resource Name'), + 'description' => $this->translate('The unique name of this resource') + ) + ); + $this->addElement( + 'text', + 'user', + array( + 'required' => true, + 'label' => $this->translate('User'), + 'description' => $this->translate( + 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be' + . ' possible for this user' + ) + ) + ); + + if ($this->getRequest()->getActionName() != 'editresource') { + + $callbackValidator = new Zend_Validate_Callback(function ($value) { + if (openssl_pkey_get_private($value) === false) { + return false; + } + return true; + }); + $callbackValidator->setMessage( + $this->translate('The given SSH key is invalid'), + Zend_Validate_Callback::INVALID_VALUE + ); + + $this->addElement( + 'textarea', + 'private_key', + array( + 'required' => true, + 'label' => $this->translate('Private Key'), + 'description' => $this->translate('The private key which will be used for the SSH connections'), + 'class' => 'resource ssh-identity', + 'validators' => array($callbackValidator) + ) + ); + } else { + $resourceName = $formData['name']; + $this->addElement( + 'note', + 'private_key_note', + array( + 'escape' => false, + 'label' => $this->translate('Private Key'), + 'value' => sprintf( + '%3$s', + $this->getView()->url('config/removeresource', array('resource' => $resourceName)), + sprintf($this->translate( + 'Remove the %s resource' + ), $resourceName), + $this->translate('To modify the private key you must recreate this resource.') + ) + ) + ); + } + + return $this; + } + + /** + * Remove the assigned key to the resource + * + * @param ConfigObject $config + * + * @return bool + */ + public static function beforeRemove(ConfigObject $config) + { + $file = $config->private_key; + + if (file_exists($file)) { + unlink($file); + return true; + } + return false; + } + + /** + * Creates the assigned key to the resource + * + * @param ResourceConfigForm $form + * + * @return bool + */ + public static function beforeAdd(ResourceConfigForm $form) + { + $configDir = Icinga::app()->getConfigDir(); + $user = $form->getElement('user')->getValue(); + + $filePath = $configDir . '/ssh/' . $user; + + if (! file_exists($filePath)) { + $file = File::create($filePath, 0600); + } else { + $form->error( + sprintf($form->translate('The private key for the user "%s" is already exists.'), $user) + ); + return false; + } + + $file->fwrite($form->getElement('private_key')->getValue()); + + $form->getElement('private_key')->setValue($configDir . '/ssh/' . $user); + + return true; + } +} diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php index bfec65938..5d6626d02 100644 --- a/application/forms/Config/ResourceConfigForm.php +++ b/application/forms/Config/ResourceConfigForm.php @@ -10,6 +10,7 @@ use Icinga\Forms\Config\Resource\DbResourceForm; use Icinga\Forms\Config\Resource\FileResourceForm; use Icinga\Forms\Config\Resource\LdapResourceForm; use Icinga\Forms\Config\Resource\LivestatusResourceForm; +use Icinga\Forms\Config\Resource\SshResourceForm; use Icinga\Application\Platform; use Icinga\Exception\ConfigurationError; @@ -41,6 +42,8 @@ class ResourceConfigForm extends ConfigForm return new LivestatusResourceForm(); } elseif ($type === 'file') { return new FileResourceForm(); + } elseif ($type === 'ssh') { + return new SshResourceForm(); } else { throw new InvalidArgumentException(sprintf($this->translate('Invalid resource type "%s" provided'), $type)); } @@ -55,7 +58,7 @@ class ResourceConfigForm extends ConfigForm * * @return $this * - * @thrwos InvalidArgumentException In case the resource does already exist + * @throws InvalidArgumentException In case the resource does already exist */ public function add(array $values) { @@ -116,6 +119,11 @@ class ResourceConfigForm extends ConfigForm } $resourceConfig = $this->config->getSection($name); + $resourceForm = $this->getResourceForm($resourceConfig->type); + if (method_exists($resourceForm, 'beforeRemove')) { + $resourceForm::beforeRemove($resourceConfig); + } + $this->config->removeSection($name); return $resourceConfig; } @@ -130,8 +138,9 @@ class ResourceConfigForm extends ConfigForm */ public function onSuccess() { + $resourceForm = $this->getResourceForm($this->getElement('type')->getValue()); + if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) { - $resourceForm = $this->getResourceForm($this->getElement('type')->getValue()); if (method_exists($resourceForm, 'isValidResource') && false === $resourceForm::isValidResource($this)) { $this->addElement($this->getForceCreationCheckbox()); return false; @@ -141,6 +150,11 @@ class ResourceConfigForm extends ConfigForm $resource = $this->request->getQuery('resource'); try { if ($resource === null) { // create new resource + if (method_exists($resourceForm, 'beforeAdd')) { + if (! $resourceForm::beforeAdd($this)) { + return false; + } + } $this->add($this->getValues()); $message = $this->translate('Resource "%s" has been successfully created'); } else { // edit existing resource @@ -212,6 +226,7 @@ class ResourceConfigForm extends ConfigForm $resourceTypes = array( 'file' => $this->translate('File'), 'livestatus' => 'Livestatus', + 'ssh' => $this->translate('SSH Identity'), ); if ($resourceType === 'ldap' || Platform::extensionLoaded('ldap')) { $resourceTypes['ldap'] = 'LDAP'; diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml index 636a449d4..20538a9b3 100644 --- a/application/views/scripts/group/show.phtml +++ b/application/views/scripts/group/show.phtml @@ -31,15 +31,16 @@ if ($this->hasPermission('config/authentication/groups/edit') && $backend instan

escape($group->group_name); ?>

translate('Created at'); ?>: created_at === null ? '-' : $this->formatDateTime($group->created_at); ?>

translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?>

+

translate('Members'); ?>

+ compact): ?> + sortBox; ?> + + limiter; ?> + paginator; ?> + compact): ?> + filterEditor; ?> + - compact): ?> - sortBox; ?> - - limiter; ?> - paginator; ?> - compact): ?> - filterEditor; ?> -
0): ?> diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml index 82f4c53f9..64a9eaa02 100644 --- a/application/views/scripts/user/show.phtml +++ b/application/views/scripts/user/show.phtml @@ -31,15 +31,16 @@ if ($this->hasPermission('config/authentication/users/edit') && $backend instanc

translate('State'); ?>: is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?>

translate('Created at'); ?>: created_at === null ? '-' : $this->formatDateTime($user->created_at); ?>

translate('Last modified'); ?>: last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?>

+

translate('Group Memberships'); ?>

+ compact): ?> + sortBox; ?> + + limiter; ?> + paginator; ?> + compact): ?> + filterEditor; ?> +
- compact): ?> - sortBox; ?> - - limiter; ?> - paginator; ?> - compact): ?> - filterEditor; ?> -
0): ?> diff --git a/doc/about.md b/doc/about.md new file mode 100644 index 000000000..48b4041ac --- /dev/null +++ b/doc/about.md @@ -0,0 +1,70 @@ +# About Icinga Web 2 + +Icinga Web 2 is a powerful PHP framework for web applications that comes in a clean and reduced design. +It's fast, responsive, accessible and easily extensible with modules. + +## The monitoring module + +This is the core module for most Icinga Web 2 users. + +It provides an intuitive user interface for monitoring with Icinga (1 and 2). +Especially there are lots of list and detail views (e.g. for hosts and services) +you can sort and filter depending on what you want to see. + +You can also control the monitoring process itself by sending external commands to Icinga. +Most such actions (like rescheduling a check) can be done with just a single click. + +## Installation + +Icinga Web 2 can be installed easily from packages from the official package repositories. +Setting it up is also easy with the web based setup wizard. + +See [here](installation#installation) for more information about the installation. + +## Configuration + +Icinga Web 2 can be configured via the user interface and .ini files. + +See [here](configuration#configuration) for more information about the configuration. + +## Authentication + +With Icinga Web 2 you can authenticate against relational databases, LDAP and more. +These authentication methods can be easily configured (via the corresponding .ini file). + +See [here](authentication#authentication) for more information about +the different authentication methods available and how to configure them. + +## Authorization + +In Icinga Web 2 there are permissions and restrictions to allow and deny (respectively) +roles to view or to do certain things. +These roles can be assigned to users and groups. + +See [here](security#security) for more information about authorization +and how to configure roles. + +## User preferences + +Besides the global configuration each user has individual configuration options +like the interface's language or the current timezone. +They can be stored either in a database or in .ini files. + +See [here](preferences#preferences) for more information about a user's preferences +and how to configure their storage type. + +## Documentation + +With the documentation module you can read the documentation of the framework (and any module) directly in the user interface. + +The module can also export the documentation to PDF. + +## Translation + +With the translation module every piece of text in the user interface (of the framework itself and any module) can be translated to a language of your choice. + +Currently provided languages: + +* German +* Italian +* Portuguese diff --git a/doc/resources.md b/doc/resources.md index 29a35c30d..a2bfb66af 100644 --- a/doc/resources.md +++ b/doc/resources.md @@ -8,7 +8,7 @@ different files, when the information about a data source changes. Each section in **config/resources.ini** represents a data source with the section name being the identifier used to reference this specific data source. Depending on the data source type, the sections define different directives. -The available data source types are *db*, *ldap* and *livestatus* which will described in detail in the following +The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following paragraphs. ### Database @@ -64,6 +64,26 @@ bind_dn = "cn=admin,ou=people,dc=icinga,dc=org" bind_pw = admin` ```` +### SSH + +A SSH resource contains the information about the user and the private key location, which can be used for the key-based +ssh authentication. + +Directive | Description +--------------------|------------ +**type** | `ssh` +**user** | The username to use when connecting to the server. +**private_key** | The path to the private key of the user. + +**Example:** + +```` +[ssh] +type = "ssh" +user = "ssh-user" +private_key = "/etc/icingaweb2/ssh/ssh-user" +```` + ### Livestatus A Livestatus resource represents the location of a Livestatus socket which is used for fetching monitoring data. diff --git a/doc/security.md b/doc/security.md new file mode 100644 index 000000000..f975c5c9b --- /dev/null +++ b/doc/security.md @@ -0,0 +1,279 @@ +# Security + +Access control is a vital part of configuring Icinga Web 2 in a secure way. +It is important that not every user that has access to Icinga Web 2 is able +to do any action or to see any host and service. For example, it is useful to allow +only a small group of administrators to change the Icinga Web 2 configuration, +to prevent misconfiguration or security breaches. Another important use case is +creating groups of users which can only see the fraction of the monitoring +environment they are in charge of. + +This chapter will describe how to do the security configuration of Icinga Web 2 +and how to apply permissions and restrictions to users or groups of users. + +## Basics + +Icinga Web 2 access control is done by defining **roles** that associate permissions +and restrictions with **users** and **groups**. There are two general kinds of +things to which access can be managed: actions and objects. + + +### Actions + +Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration, +changing permissions or sending a command to the Icinga instance through the +Command Pipe +in the monitoring module. All actions must be be **allowed explicitly** using permissions. + +A permission is a simple list of identifiers of actions a user is +allowed to do. Permissions are described in greater detail in the +section [Permissions](#permissions). + +### Objects + +There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events. + +By default, a user can **see everything**, but it is possible to **explicitly restrict** what each user can see using restrictions. + +Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described +in greater detail in the section [Restrictions](#restrictions). + +### Users + +Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the +**user name** used during login. +For example, there might be user called **jdoe** authenticated +using Active Directory, and a user **icingaadmin** that is authenticated using a MySQL-Database as backend. +In the configuration, both can be referenced to by using their user names **icingaadmin** or **jdoe**. + +Icinga Web 2 users and groups are not configured by a configuration file, but provided by +an **authentication backend**. For extended information on setting up authentication backends and managing users, please read the chapter [Authentication](authentication.md#authentication). + + +
+ Since Icinga Web 2, users in the Icinga configuration and the web authentication are separated, to allow + use of external authentication providers. This means that users and groups defined in the Icinga configuration are not available to Icinga Web 2. Instead it uses its own authentication + backend to fetch users and groups from, which must be configured separately. +
+ +#### Managing Users + +When using a [Database +as authentication backend](authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration +can be found at **Configuration > Authentication > Users **. + +### Groups + +If there is a big amount of users to manage, it would be tedious to specify each user +separately when regularly referring to the same group of users. Because of that, it is possible to group users. +A user can be member of multiple groups and will inherit all permissions and restrictions. + +Like users, groups are identified solely by their **name** that is provided by + a **group backend**. For extended information on setting up group backends, + please read the chapter [Authentication](authentication.md#authentication). + + +#### Managing Groups + +When using a [Database as an authentication backend](#authentication.md#authentication-configuration-db-authentication), +it is possible to manage groups and group memberships directly in the frontend. This configuration +can be found at **Configuration > Authentication > Groups **. + +## Roles + +A role defines a set of **permissions** and **restrictions** and assigns +those to **users** and **groups**. For example, a role **admins** could define that certain +users have access to all configuration options, or another role **support** +could define that a list of users or groups is restricted to see only hosts and services +that match a specific query. + +The actual permission of a certain user will be determined by merging the permissions +and restrictions of the user itself and all the groups the user is member of. Permissions can +be simply added up, while restrictions follow a slighty more complex pattern, that is described +in the section [Stacking Filters](#stacking-filters). + +### Configuration + +Roles can be changed either through the icingaweb2 interface, by navigation +to the page **Configuration > Authentication > Roles**, or through editing the +configuration file: + + + /etc/icingaweb2/roles.ini + + +#### Introducing Example + +To get you a quick start, here is an example of what a role definition could look like: + + + [winadmin] + users = "jdoe, janedoe" + groups = "admin" + permissions = "config/application/*, monitoring/commands/schedule-check" + monitoring/filter/objects = "host=*win*" + + +This example creates a role called **winadmin**, that grants all permissions in `config/application/*` and `monitoring/commands/schedule-check` and additionally only +allows the hosts and services that match the filter `host=*win*` to be displayed. The users +**jdoe** and **janedoe** and all members of the group **admin** will be affected +by this role. + + +#### Syntax + +Each role is defined as a section, with the name of the role as section name. The following +attributes can be defined for each role in a default Icinga Web 2 installation: + + + Directive | Description +---------------------------|----------------------------------------------------------------------------- + users | A comma-separated list of user **user names** that are affected by this role + groups | A comma-separated list of **group names** that are affected by this role + permissions | A comma-separated list of **permissions** granted by this role + monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts + + + +## Permissions + +Permissions can be used to allow users or groups certain **actions**. By default, +all actions are **prohibited** and must be allowed explicitly by a role for any user. + +Each action in Icinga Web 2 is denoted by a **namespaced key**, which is used to order and +group those actions. All actions that affect the configuration of Icinga Web 2, are in a +namespace called **config**, while all configurations that affect authentication +are in the namespace `config/authentication` + +**Wildcards** can be used to grant permission for all actions in a certain namespace. +The permission `config/*` would grant permission to all configuration actions, +while just specifying a wildcard `*` would give permission for all actions. + +When multiple roles assign permissions to the same user (either directly or indirectly +through a group) all permissions can simply be added together to get the users actual permission set. + +#### Global permissions + + Name | Permits +-------------------------------------|----------------------------------------------------------------- + * | Allow everything, including module-specific permissions + config/* | Allow all configuration actions + config/application/* | Allow configuring IcingaWeb2 + config/application/general | Allow general settings, like logging or preferences + config/application/resources | Allow changing resources for retrieving data + config/application/userbackend | Allow changing backends for retrieving available users + config/application/usergroupbackend | Allow changing backends for retrieving available groups + config/authentication/* | Allow configuring IcingaWeb2 authentication mechanisms + config/authentication/users/* | Allow all user actions + config/authentication/users/show | Allow displaying avilable users + config/authentication/users/add | Allow adding a new user to the backend + config/authentication/users/edit | Allow editing an existing user in the backend + config/authentication/users/remove | Allow removing an existing user from the backend + config/authentication/groups/* | Allow all group actions + config/authentication/groups/show | Allow displaying all available groups + config/authentication/groups/add | Allow adding a new group to the backend + config/authentication/groups/edit | Allow editing existing groups in a backend + config/authentication/groups/remove | Allow removing existing groups from the backend + config/authentication/roles/* | Allow all role actions + config/authentication/roles/add | Allow adding a new role + config/authentication/roles/show | Allow displaying available roles + config/authentication/roles/edit | Allow changing an existing role + config/authentication/roles/remove | Allow removing an existing row + config/modules | Allow enabling or disabling modules + + +#### Monitoring module permissions + +The built-in monitoring module defines an additional set of permissions, that +is described in detail in [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security). + + +## Restrictions + +Restrictions can be used to define what a user or group can see by specifying +a filter expression that applies to a defined set of data. By default, when no +restrictions are defined, a user will be able to see every information that is available. + +A restrictions is always specified for a certain **filter directive**, that defines what +data the filter is applied to. The **filter directive** is a simple identifier, that was +defined in an Icinga Web 2 module. The only filter directive that is available +in a default installation, is the `monitoring/filter/objects` directive, defined by the monitoring module, +that can be used to apply filter to hosts and services. This directive was previously +mentioned in the section [Syntax](#syntax). + +### Filter Expressions + +Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in +the [monitoring module documentation](/icingaweb2/doc/module/doc/chapter/monitoring-security#monitoring-security-restrictions). + +Any filter expression that is allowed in the filtered view, is also an allowed filter expression. +This means, that it is possible to define negations, wildcards, and even nested +filter expressions containing AND and OR-Clauses. + +The filter expression will be **implicitly** added as an **AND-Clause** to each query on +the filtered data. The following shows the filter expression `host=*win*` being applied on `monitoring/filter/objects`. + + +Regular filter query: + + AND-- service_problem = 1 + | + +--- service_handled = 0 + + +With our restriction applied, any user affected by this restrictions will see the +results of this query instead: + + + AND-- host = *win* + | + +--AND-- service_problem = 1 + | + +--- service_handled = 0 + + +#### Stacking Filters + +When multiple roles assign restrictions to the same user, either directly or indirectly +through a group, all filters will be combined using an **OR-Clause**, resulting in the final +expression: + + + AND-- OR-- $FILTER1 + | | + | +-- $FILTER2 + | | + | +-- $FILTER3 + | + +--AND-- service_problem = 1 + | + +--- service_handled = 0 + + +As a result, a user is be able to see hosts that are matched by **ANY** of +the filter expressions. The following examples will show the usefulness of this behavior: + +#### Example 1: Negation + + [winadmin] + groups = "windows-admins" + monitoring/filter/objects = "host=*win*" + +Will display only hosts and services whose host name contains **win**. + + [webadmin] + groups = "web-admins" + monitoring/filter/objects = "host!=*win*" + +Will only match hosts and services whose host name does **not** contain **win** + +Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services. + +#### Example 2: Hostgroups + + [unix-server] + groups = "unix-admins" + monitoring/filter/objects = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)" + +This role allows all members of the group unix-admins to see hosts and services +that are part of the host-group linux-servers or the host-group bsd-servers. \ No newline at end of file diff --git a/icingaweb2.spec b/icingaweb2.spec index c7aa4cb8d..84c3e0b13 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -201,7 +201,7 @@ cp -prv packages/files/config/modules/setup %{buildroot}/%{configdir}/modules/ %pre getent group icingacmd >/dev/null || groupadd -r icingacmd -%if 0%{?suse_version} +%if 0%{?suse_version} && 0%{?suse_version} < 01200 usermod -A icingacmd,%{icingawebgroup} %{wwwuser} %else usermod -a -G icingacmd,%{icingawebgroup} %{wwwuser} diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index e265b91ee..6f409789a 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -134,18 +134,20 @@ abstract class ApplicationBootstrap $this->vendorDir = $baseDir . '/library/vendor'; $this->libDir = realpath(__DIR__ . '/../..'); + $this->setupAutoloader(); + if ($configDir === null) { if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; } else { - $configDir = '/etc/icingaweb2'; + $configDir = Platform::isWindows() + ? $baseDir . '/config' + : '/etc/icingaweb2'; } } $canonical = realpath($configDir); $this->configDir = $canonical ? $canonical : $configDir; - $this->setupAutoloader(); - set_include_path( implode( PATH_SEPARATOR, diff --git a/library/Icinga/Application/webrouter.php b/library/Icinga/Application/webrouter.php index 905fa7dca..5a7d1c95c 100644 --- a/library/Icinga/Application/webrouter.php +++ b/library/Icinga/Application/webrouter.php @@ -31,7 +31,7 @@ $baseDir = $_SERVER['DOCUMENT_ROOT']; $baseDir = dirname($_SERVER['SCRIPT_FILENAME']); // Fix aliases -$remove = dirname($_SERVER['PHP_SELF']); +$remove = str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])); if (substr($ruri, 0, strlen($remove)) !== $remove) { return false; } diff --git a/library/Icinga/Cli/Params.php b/library/Icinga/Cli/Params.php index 9e30cf19e..85d5cd26f 100644 --- a/library/Icinga/Cli/Params.php +++ b/library/Icinga/Cli/Params.php @@ -47,7 +47,12 @@ class Params $noOptionFlag = true; } elseif (!$noOptionFlag && substr($argv[$i], 0, 2) === '--') { $key = substr($argv[$i], 2); - if (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') { + $matches = array(); + if (1 === preg_match( + '/(?params[$matches[1]] = $matches[2]; + } elseif (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') { $this->params[$key] = true; } elseif (array_key_exists($key, $this->params)) { if (!is_array($this->params[$key])) { diff --git a/library/Icinga/Util/File.php b/library/Icinga/Util/File.php index 8dd436137..58104e2cc 100644 --- a/library/Icinga/Util/File.php +++ b/library/Icinga/Util/File.php @@ -63,7 +63,7 @@ class File extends SplFileObject throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath)); } - $file = new static($path, 'x'); + $file = new static($path, 'x+'); if (! @chmod($path, $accessMode)) { $error = error_get_last(); diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php index 67825b63b..03fd865cd 100644 --- a/library/Icinga/Util/Translator.php +++ b/library/Icinga/Util/Translator.php @@ -100,7 +100,11 @@ class Translator { $contextString = "{$context}\004{$text}"; - $translation = dcgettext($domain, $contextString, LC_MESSAGES); + $translation = dcgettext( + $domain, + $contextString, + defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL + ); if ($translation == $contextString) { return $text; @@ -126,7 +130,13 @@ class Translator { $contextString = "{$context}\004{$textSingular}"; - $translation = dcngettext($domain, $contextString, $textPlural, $number, LC_MESSAGES); + $translation = dcngettext( + $domain, + $contextString, + $textPlural, + $number, + defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL + ); if ($translation == $contextString || $translation == $textPlural) { return ($number == 1 ? $textSingular : $textPlural); diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php index 45dd2dd9c..a201b34fc 100644 --- a/library/Icinga/Web/Menu.php +++ b/library/Icinga/Web/Menu.php @@ -237,6 +237,10 @@ class Menu implements RecursiveIterator 'priority' => 700, 'renderer' => 'ProblemMenuItemRenderer' )); + $section->add(t('About'), array( + 'url' => 'about', + 'priority' => 701 + )); if (Logger::writesToFile()) { $section->add(t('Application Log'), array( 'url' => 'list/applicationlog', @@ -279,11 +283,6 @@ class Menu implements RecursiveIterator 'priority' => 990, 'renderer' => 'ForeignMenuItemRenderer' )); - - $this->add(t('About'), array( - 'url' => 'about', - 'priority' => 1000 - )); } } diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php index d97f83b39..504c076be 100644 --- a/library/Icinga/Web/UrlParams.php +++ b/library/Icinga/Web/UrlParams.php @@ -374,9 +374,29 @@ class UrlParams } } - public function toArray() + /** + * Return the parameters of this url as sequenced or associative array + * + * @param bool $sequenced + * + * @return array + */ + public function toArray($sequenced = true) { - return $this->params; + if ($sequenced) { + return $this->params; + } + + $params = array(); + foreach ($this->params as $param) { + if ($param[1] === true) { + $params[] = $param[0]; + } else { + $params[$param[0]] = $param[1]; + } + } + + return $params; } public function toString($separator = null) diff --git a/library/Icinga/Web/Widget/Tabs.php b/library/Icinga/Web/Widget/Tabs.php index aa5e7cda6..6b38f9096 100644 --- a/library/Icinga/Web/Widget/Tabs.php +++ b/library/Icinga/Web/Widget/Tabs.php @@ -309,13 +309,14 @@ EOT; private function renderRefreshTab() { - $url = Url::fromRequest()->without('renderLayout'); $tab = $this->get($this->getActiveName()); - if ($tab !== null) { + $url = Url::fromRequest($tab->getUrl()->getParams()->toArray(false))->without('renderLayout'); $label = $this->view()->escape( $tab->getLabel() ); + } else { + $url = Url::fromRequest()->without('renderLayout'); } if (! empty($label)) { diff --git a/modules/doc/doc/security.md b/modules/doc/doc/security.md new file mode 100644 index 000000000..023a66410 --- /dev/null +++ b/modules/doc/doc/security.md @@ -0,0 +1,65 @@ +# Security + +The monitoring module provides an additional set of restrictions and permissions +that can be used for access control. The following sections will list those +restrictions and permissions in detail: + + +## Permissions + +The Icinga Web 2 monitoring module can send commands to the current Icinga2 instance +through the command pipe. A user needs specific permissions to be able to send those +commands when using the monitoring module. + + +| Name | Permits | +|---------------------------------------------|-----------------------------------------------------------------------------| +| monitoring/command/* | Allow all commands | +| monitoring/command/schedule-check | Allow scheduling host and service checks' | +| monitoring/command/acknowledge-problem | Allow acknowledging host and service problems | +| monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements | +| monitoring/command/comment/* | Allow adding and deleting host and service comments | +| monitoring/command/comment/add | Allow commenting on hosts and services | +| monitoring/command/downtime/delete | Allow deleting host and service downtimes' | +| monitoring/command/process-check-result | Allow processing host and service check results | +| monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis | +| monitoring/command/feature/object | Allow processing commands for toggling features on host and service objects | +| monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services | + + +## Restrictions + +The monitoring module allows filtering objects: + + +| Keys | Restricts | +|----------------------------|-----------------------------------------------| +| monitoring/filter/objects | Applies a filter to all hosts and services | + + +This filter will affect all hosts and services. Furthermore, it will also +affect all related objects, like notifications, downtimes or events. If a +service is hidden, all notifications, downtimes on that service will be hidden too. + + +### Filter Column Names + +The following filter column names are available in filter expressions: + + +| Column | +|------------------------------------------------------| +| host | +| host_alias | +| host_display_name | +| host_name | +| hostgroup | +| hostgroup_alias | +| hostgroup_name | +| service | +| service_description | +| service_display_name | +| service_group | +| service_group_alias | +| service_group_name | +| + all custom variables prefixed with host or service | diff --git a/modules/doc/module.info b/modules/doc/module.info index 2c6f48ac9..32516de5b 100644 --- a/modules/doc/module.info +++ b/modules/doc/module.info @@ -1,4 +1,4 @@ Module: doc -Version: 2.0.0 +Version: 2.0.0-rc1 Description: Documentation module Extracts, shows and exports documentation for Icinga Web 2 and its modules. diff --git a/modules/monitoring/application/clicommands/ConferenceCommand.php b/modules/monitoring/application/clicommands/ConferenceCommand.php index c7c7f1292..d0062b1a9 100644 --- a/modules/monitoring/application/clicommands/ConferenceCommand.php +++ b/modules/monitoring/application/clicommands/ConferenceCommand.php @@ -20,7 +20,7 @@ class ConferenceCommand extends Command * Use this command in case you feel that you should be friendly. Should * be executed as follows: * - * icingacli monitoring conference welcome --watch 1 + * icingacli monitoring conference welcome --watch=1 */ public function welcomeAction() { diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php index c3f4ab951..8e2ad6732 100644 --- a/modules/monitoring/application/clicommands/ListCommand.php +++ b/modules/monitoring/application/clicommands/ListCommand.php @@ -5,7 +5,7 @@ namespace Icinga\Module\Monitoring\Clicommands; use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Cli\CliUtils; -use Icinga\Util\Format; +use Icinga\Date\DateFormatter; use Icinga\Cli\Command; use Icinga\File\Csv; use Icinga\Module\Monitoring\Plugin\PerfdataSet; @@ -124,19 +124,19 @@ class ListCommand extends Command * --verbose Show detailled output * --showsql Dump generated SQL query (DB backend only) * - * --format > + * --format=> * Dump columns in the given format. format allows $column$ - * placeholders, e.g. --format '$host$: $service$' + * placeholders, e.g. --format='$host$: $service$' * - * -- [filter] + * --[=filter] * Filter given column by optional filter. Boolean (1/0) columns are * true if no filter value is given. * * EXAMPLES * * icingacli monitoring list --unhandled - * icingacli monitoring list --host local* --service *disk* - * icingacli monitoring list --format '$host$: $service$' + * icingacli monitoring list --host=local* --service=*disk* + * icingacli monitoring list --format='$host$: $service$' */ public function statusAction() { @@ -299,7 +299,7 @@ class ListCommand extends Command $leaf, $screen->underline($row->service_description), $screen->colorize($utils->objectStateFlags('service', $row) . $perf, 'lightblue'), - ucfirst(Format::timeSince($row->service_last_state_change)) + ucfirst(DateFormatter::timeSince($row->service_last_state_change)) ); if ($this->isVerbose) { $out .= $emptyLine . preg_replace( diff --git a/modules/monitoring/application/clicommands/NrpeCommand.php b/modules/monitoring/application/clicommands/NrpeCommand.php index 190cdbe85..abb877553 100644 --- a/modules/monitoring/application/clicommands/NrpeCommand.php +++ b/modules/monitoring/application/clicommands/NrpeCommand.php @@ -27,8 +27,8 @@ class NrpeCommand extends Command * * EXAMPLE * - * icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn 80% \ - * --MaxCrit 90% --type physical + * icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn=80% \ + * --MaxCrit=90% --type=physical */ public function checkAction() { diff --git a/modules/monitoring/application/controllers/HostController.php b/modules/monitoring/application/controllers/HostController.php index c006bbe8f..a7659726e 100644 --- a/modules/monitoring/application/controllers/HostController.php +++ b/modules/monitoring/application/controllers/HostController.php @@ -63,6 +63,54 @@ class Monitoring_HostController extends MonitoredObjectController parent::showAction(); } + /** + * List a host's services + */ + public function servicesAction() + { + $this->setAutorefreshInterval(10); + $this->getTabs()->activate('services'); + $query = $this->backend->select()->from('servicestatus', array( + 'host_name', + 'host_display_name', + 'host_state', + 'host_state_type', + 'host_last_state_change', + 'host_address', + 'host_handled', + 'service_description', + 'service_display_name', + 'service_state', + 'service_in_downtime', + 'service_acknowledged', + 'service_handled', + 'service_output', + 'service_perfdata', + 'service_attempt', + 'service_last_state_change', + 'service_icon_image', + 'service_icon_image_alt', + 'service_is_flapping', + 'service_state_type', + 'service_handled', + 'service_severity', + 'service_last_check', + 'service_notifications_enabled', + 'service_action_url', + 'service_notes_url', + 'service_last_comment', + 'service_last_ack', + 'service_last_downtime', + 'service_active_checks_enabled', + 'service_passive_checks_enabled', + 'current_check_attempt' => 'service_current_check_attempt', + 'max_check_attempts' => 'service_max_check_attempts' + )); + $this->applyRestriction('monitoring/filter/objects', $query); + $this->view->services = $query->where('host_name', $this->object->getName()); + $this->view->object = $this->object; + } + /** * Acknowledge a host problem */ diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index 9475baff6..cfed8bd0d 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -1,12 +1,7 @@ view->object = MonitoredObject::fromParams($this->params); - if ($this->view->object && $this->view->object->fetch() === false) { - throw new Zend_Controller_Action_Exception($this->translate('Host or service not found')); - } - - if (Hook::has('ticket')) { - $this->view->tickets = Hook::first('ticket'); - } - if (Hook::has('grapher')) { - $this->grapher = Hook::first('grapher'); - if ($this->grapher && ! $this->grapher->hasPreviews()) { - $this->grapher = null; - } - } - - $this->createTabs(); - } - /** * @deprecated */ @@ -66,36 +33,16 @@ class Monitoring_ShowController extends Controller $this->redirectNow(Url::fromRequest()->setPath('monitoring/host/show')); } + /** + * @deprecated + */ public function historyAction() { - $this->getTabs()->activate('history'); - $this->view->object->fetchEventHistory(); - $this->view->history = $this->view->object->eventhistory; - $this->handleFormatRequest($this->view->object->eventhistory); - $this->fetchHostStats(); + if ($this->params->has('service')) { + $this->redirectNow(Url::fromRequest()->setPath('monitoring/service/history')); + } - $this->setupLimitControl(50); - $this->setupPaginationControl($this->view->history, 50); - } - - protected function fetchHostStats() - { - $query = $this->backend->select()->from('servicestatussummary', array( - 'services_total', - 'services_ok', - 'services_critical', - 'services_critical_unhandled', - 'services_critical_handled', - 'services_warning', - 'services_warning_unhandled', - 'services_warning_handled', - 'services_unknown', - 'services_unknown_unhandled', - 'services_unknown_handled', - 'services_pending', - ))->where('service_host_name', $this->params->get('host')); - $this->applyRestriction('monitoring/filter/objects', $query); - $this->view->stats = $query->getQuery()->fetchRow(); + $this->redirectNow(Url::fromRequest()->setPath('monitoring/host/history')); } public function contactAction() @@ -163,78 +110,4 @@ class Monitoring_ShowController extends Controller $this->view->contact = $contact; $this->view->contactName = $contactName; } - - /** - * Creating tabs for this controller - * @return Tabs - */ - protected function createTabs() - { - if (($object = $this->view->object) === null) { - return; - } - if ($object->getType() === $object::TYPE_HOST) { - $isService = false; - $params = array( - 'host' => $object->getName() - ); - } else { - $isService = true; - $params = array( - 'host' => $object->getHost()->getName(), - 'service' => $object->getName() - ); - } - $tabs = $this->getTabs(); - $tabs->add( - 'host', - array( - 'title' => sprintf( - $this->translate('Show detailed information for host %s'), - $isService ? $object->getHost()->getName() : $object->getName() - ), - 'label' => $this->translate('Host'), - 'icon' => 'host', - 'url' => 'monitoring/show/host', - 'urlParams' => $params, - ) - ); - if ($isService) { - $tabs->add( - 'service', - array( - 'title' => sprintf( - $this->translate('Show detailed information for service %s on host %s'), - $object->getName(), - $object->getHost()->getName() - ), - 'label' => $this->translate('Service'), - 'icon' => 'service', - 'url' => 'monitoring/show/service', - 'urlParams' => $params, - ) - ); - } - if ($this->backend->hasQuery('eventhistory')) { - $tabs->add( - 'history', - array( - 'title' => $isService - ? sprintf( - $this->translate('Show all event records of service %s on host %s'), - $object->getName(), - $object->getHost()->getName() - ) - : sprintf($this->translate('Show all event records of host %s'), $object->getName()) - , - 'label' => $this->translate('History'), - 'icon' => 'rewind', - 'url' => 'monitoring/show/history', - 'urlParams' => $params, - ) - ); - } - $tabs->extend(new OutputFormat()) - ->extend(new DashboardAction()); - } } diff --git a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php b/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php index 8a1a34b40..244bf66d1 100644 --- a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php +++ b/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php @@ -3,10 +3,19 @@ namespace Icinga\Module\Monitoring\Forms\Config\Instance; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; use Icinga\Web\Form; class RemoteInstanceForm extends Form { + /** + * The available monitoring instance resources split by type + * + * @var array + */ + protected $resources; + /** * (non-PHPDoc) * @see Form::init() For the method documentation. @@ -16,12 +25,89 @@ class RemoteInstanceForm extends Form $this->setName('form_config_monitoring_instance_remote'); } + /** + * Load all available ssh identity resources + * + * @return $this + * + * @throws \Icinga\Exception\ConfigurationError + */ + public function loadResources() + { + $resourceConfig = ResourceFactory::getResourceConfigs(); + + $resources = array(); + foreach ($resourceConfig as $name => $resource) { + if ($resource->type === 'ssh') { + $resources['ssh'][$name] = $name; + } + } + + if (empty($resources)) { + throw new ConfigurationError($this->translate('Could not find any valid monitoring instance resources')); + } + + $this->resources = $resources; + + return $this; + } + /** * (non-PHPDoc) * @see Form::createElements() For the method documentation. */ public function createElements(array $formData = array()) { + $useResource = isset($formData['use_resource']) ? $formData['use_resource'] : $this->getValue('use_resource'); + + $this->addElement( + 'checkbox', + 'use_resource', + array( + 'label' => $this->translate('Use SSH Identity'), + 'description' => $this->translate('Make use of the ssh identity resource'), + 'autosubmit' => true, + 'ignore' => true + ) + ); + + if ($useResource) { + + $this->loadResources(); + + $decorators = static::$defaultElementDecorators; + array_pop($decorators); // Removes the HtmlTag decorator + + $this->addElement( + 'select', + 'resource', + array( + 'required' => true, + 'label' => $this->translate('SSH Identity'), + 'description' => $this->translate('The resource to use'), + 'decorators' => $decorators, + 'multiOptions' => $this->resources['ssh'], + 'value' => current($this->resources['ssh']), + 'autosubmit' => false + ) + ); + $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource'); + $this->addElement( + 'note', + 'resource_note', + array( + 'escape' => false, + 'decorators' => $decorators, + 'value' => sprintf( + '%3$s', + $this->getView()->url('config/editresource', array('resource' => $resourceName)), + sprintf($this->translate('Show the configuration of the %s resource'), $resourceName), + $this->translate('Show resource configuration') + ) + ) + ); + } + $this->addElements(array( array( 'text', @@ -43,8 +129,11 @@ class RemoteInstanceForm extends Form 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'), 'value' => 22 ) - ), - array( + ) + )); + + if (! $useResource) { + $this->addElement( 'text', 'user', array( @@ -55,18 +144,20 @@ class RemoteInstanceForm extends Form . ' possible for this user' ) ) - ), + ); + } + + $this->addElement( + 'text', + 'path', array( - 'text', - 'path', - array( - 'required' => true, - 'label' => $this->translate('Command File'), - 'value' => '/var/run/icinga2/cmd/icinga2.cmd', - 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance') - ) + 'required' => true, + 'label' => $this->translate('Command File'), + 'value' => '/var/run/icinga2/cmd/icinga2.cmd', + 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance') ) - )); + ); + return $this; } } diff --git a/modules/monitoring/application/forms/Config/InstanceConfigForm.php b/modules/monitoring/application/forms/Config/InstanceConfigForm.php index b2baea89c..c44e480ac 100644 --- a/modules/monitoring/application/forms/Config/InstanceConfigForm.php +++ b/modules/monitoring/application/forms/Config/InstanceConfigForm.php @@ -143,6 +143,11 @@ class InstanceConfigForm extends ConfigForm $instanceConfig = $this->config->getSection($instanceName)->toArray(); $instanceConfig['name'] = $instanceName; + + if (isset($instanceConfig['resource'])) { + $instanceConfig['use_resource'] = true; + } + $this->populate($instanceConfig); } } diff --git a/modules/monitoring/application/views/scripts/show/history.phtml b/modules/monitoring/application/views/scripts/host/history.phtml similarity index 92% rename from modules/monitoring/application/views/scripts/show/history.phtml rename to modules/monitoring/application/views/scripts/host/history.phtml index 5c1a1d27c..36d43ee95 100644 --- a/modules/monitoring/application/views/scripts/show/history.phtml +++ b/modules/monitoring/application/views/scripts/host/history.phtml @@ -3,17 +3,12 @@ use Icinga\Module\Monitoring\Object\Host; use Icinga\Module\Monitoring\Object\Service; $self = $this; -$hostContext = $object->getType() === 'host'; if (! $this->compact): ?>
tabs; ?> - render('partials/host/object-header.phtml'); ?> - - render('partials/service/object-header.phtml'); ?> - -

translate('This Object\'s Event History'); ?>

+

translate('This Host\'s Event History'); ?>

sortBox; ?> limiter; ?> paginator; ?> @@ -24,7 +19,7 @@ if (! $this->compact): ?> translate('No history available for this object') . '
'; + echo $this->translate('No history events found matching the filter') . '
'; return; } ?> @@ -133,7 +128,7 @@ $output = $this->tickets ? preg_replace_callback( translate('%s on %s', 'Service running on host'), - $hostContext ? $this->qlink( + $this->qlink( $event->service_display_name, 'monitoring/show/service', array( @@ -145,7 +140,7 @@ $output = $this->tickets ? preg_replace_callback( $event->service_display_name, $event->host_display_name )) - ) : $this->escape($event->service_display_name), + ), $event->host_display_name ) ?> diff --git a/modules/monitoring/application/views/scripts/host/services.phtml b/modules/monitoring/application/views/scripts/host/services.phtml new file mode 100644 index 000000000..ca12f3d0e --- /dev/null +++ b/modules/monitoring/application/views/scripts/host/services.phtml @@ -0,0 +1,17 @@ +
+ compact): ?> + tabs; ?> + + render('partials/host/object-header.phtml') ?> + render('partials/host/servicesummary.phtml') ?> +
+partial( + 'list/services.phtml', + 'monitoring', + array( + 'compact' => true, + 'showHost' => false, + 'services' => $services, + 'addColumns' => array() + ) +); ?> \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/service/history.phtml b/modules/monitoring/application/views/scripts/service/history.phtml new file mode 100644 index 000000000..dd6a0b3d9 --- /dev/null +++ b/modules/monitoring/application/views/scripts/service/history.phtml @@ -0,0 +1,151 @@ +compact): ?> +
+ tabs; ?> + render('partials/service/object-header.phtml'); ?> +

translate('This Service\'s Event History'); ?>

+ sortBox; ?> + limiter; ?> + paginator; ?> + filterEditor; ?> +
+ +
+translate('No history events found matching the filter') . '
'; + return; +} +?> + +qlink( + $contact, + 'monitoring/show/contact', + array('contact_name' => $contact), + array('title' => sprintf($view->translate('Show detailed information about %s'), $contact)) + ); + } + return '[' . implode(', ', $links) . ']'; +} +?> + + + + + type) { + case 'notify': + $icon = 'notification'; + $title = $this->translate('Notification'); + $stateClass = Service::getStateText($event->state); + + $msg = preg_replace_callback( + '/^\[([^\]]+)\]/', + function($match) use ($self) { return contactsLink($match, $self); }, + $this->escape($event->output) + ); + break; + case 'comment': + $icon = 'comment'; + $title = $this->translate('Comment'); + $msg = $this->escape($event->output); + break; + case 'comment_deleted': + $icon = 'remove'; + $title = $this->translate('Comment deleted'); + $msg = $this->escape($event->output); + break; + case 'ack': + $icon = 'acknowledgement'; + $title = $this->translate('Acknowledge'); + $msg = $this->escape($event->output); + break; + case 'ack_deleted': + $icon = 'remove'; + $title = $this->translate('Ack removed'); + $msg = $this->escape($event->output); + break; + case 'dt_comment': + $icon = 'in_downtime'; + $title = $this->translate('In Downtime'); + $msg = $this->escape($event->output); + break; + case 'dt_comment_deleted': + $icon = 'remove'; + $title = $this->translate('Downtime removed'); + $msg = $this->escape($event->output); + break; + case 'flapping': + $icon = 'flapping'; + $title = $this->translate('Flapping'); + $msg = $this->escape($event->output); + break; + case 'flapping_deleted': + $icon = 'remove'; + $title = $this->translate('Flapping stopped'); + $msg = $this->escape($event->output); + break; + case 'hard_state': + $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output); + $stateClass = Service::getStateText($event->state); + $icon = 'attention-alt'; + $title = Service::getStateText($event->state); + break; + case 'soft_state': + $icon = 'spinner'; + $msg = '[ ' . $event->attempt . '/' . $event->max_attempts . ' ] ' . $this->escape($event->output); + $stateClass = Service::getStateText($event->state); + $title = Service::getStateText($event->state); + break; + case 'dt_start': + $icon = 'downtime_start'; + $title = $this->translate('Downtime Start'); + $msg = $this->escape($event->output); + break; + case 'dt_end': + $icon = 'downtime_end'; + $title = $this->translate('Downtime End'); + $msg = $this->escape($event->output); + break; + } + ?> + + + + + + +
+ escape($title); ?> +
+ timestamp); ?> +
tickets ? preg_replace_callback( + $this->tickets->getPattern(), + array($this->tickets, 'createLink'), + $msg +) : $msg; + +?> + translate('%s on %s', 'Service running on host'), + $this->escape($event->service_display_name), + $event->host_display_name + ) ?> +
+
+ icon($icon, $title); ?> +
+
+ \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/show/components/notifications.phtml b/modules/monitoring/application/views/scripts/show/components/notifications.phtml index 5fea60cc1..9a9195862 100644 --- a/modules/monitoring/application/views/scripts/show/components/notifications.phtml +++ b/modules/monitoring/application/views/scripts/show/components/notifications.phtml @@ -9,7 +9,7 @@ echo $this->qlink( $this->translate('Send notification'), 'monitoring/host/send-custom-notification', - array('host_name' => $object->getName()), + array('host' => $object->getName()), array( 'icon' => 'bell', 'data-base-target' => '_self', @@ -23,7 +23,7 @@ echo $this->qlink( $this->translate('Send notification'), 'monitoring/service/send-custom-notification', - array('host_name' => $object->getHost()->getName(), 'service_description' => $object->getName()), + array('host' => $object->getHost()->getName(), 'service' => $object->getName()), array( 'icon' => 'bell', 'data-base-target' => '_self', diff --git a/modules/monitoring/doc/instances.md b/modules/monitoring/doc/instances.md index dd04ea30c..ea8b3f5b8 100644 --- a/modules/monitoring/doc/instances.md +++ b/modules/monitoring/doc/instances.md @@ -3,7 +3,8 @@ ## Abstract The instance.ini defines how icingaweb accesses the command pipe of your icinga process in order to submit external -commands. When you are at the root of your icingaweb installation you can find it under ./config/modules/monitoring/instances.ini. +commands. Depending on the config path (default: /etc/icingaweb2) of your icingaweb installation you can find it +under ./modules/monitoring/instances.ini. ## Syntax @@ -33,5 +34,22 @@ setup key authentication at the endpoint and allow your icingweb's user to acces port=22 ; the port to use (22 if none is given) user=jdoe ; the user to authenticate with +You can also make use of the ssh resource for accessing an icinga pipe with key-based authentication, which will give +you the possibility to define the location of the private key for a specific user, let's have a look: + + [icinga] + path=/usr/local/icinga/var/rw/icinga.cmd ; the path on the remote machine where the icinga.cmd can be found + host=my.remote.machine.com ; the hostname or address of the remote machine + port=22 ; the port to use (22 if none is given) + resource=ssh ; the ssh resource which contains the username and the location of the private key + +And the associated ssh resource: + + [ssh] + type = "ssh" + user = "ssh-user" + private_key = "/etc/icingaweb2/ssh/ssh-user" + + diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php index c0f670bfe..77d85c083 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Monitoring\Command\Transport; use Icinga\Application\Logger; +use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; use Icinga\Module\Monitoring\Command\Exception\TransportException; use Icinga\Module\Monitoring\Command\IcingaCommand; @@ -44,6 +45,13 @@ class RemoteCommandFile implements CommandTransportInterface */ protected $user; + /** + * Path to the private key file for the key-based authentication + * + * @var string + */ + protected $privateKey; + /** * Path to the Icinga command file on the remote host * @@ -137,6 +145,55 @@ class RemoteCommandFile implements CommandTransportInterface return $this->user; } + /** + * Set the path to the private key file + * + * @param string $privateKey + * + * @return $this + */ + public function setPrivateKey($privateKey) + { + $this->privateKey = (string) $privateKey; + return $this; + } + + /** + * Get the path to the private key + * + * @return string + */ + public function getPrivateKey() + { + return $this->privateKey; + } + + /** + * Use a given resource to set the user and the key + * + * @param string + * + * @throws ConfigurationError + */ + public function setResource($resource = null) + { + $config = ResourceFactory::getResourceConfig($resource); + + if (! isset($config->user)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. Remote user is missing") + ); + } + if (! isset($config->private_key)) { + throw new ConfigurationError( + t("Can't send external Icinga Command. The private key for the remote user is missing") + ); + } + + $this->setUser($config->user); + $this->setPrivateKey($config->private_key); + } + /** * Set the path to the Icinga command file on the remote host * @@ -192,6 +249,9 @@ class RemoteCommandFile implements CommandTransportInterface if (isset($this->user)) { $ssh .= sprintf(' -l %s', escapeshellarg($this->user)); } + if (isset($this->privateKey)) { + $ssh .= sprintf(' -o StrictHostKeyChecking=no -i %s', escapeshellarg($this->privateKey)); + } $ssh .= sprintf( ' %s "echo %s > %s" 2>&1', // Redirect stderr to stdout escapeshellarg($this->host), diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php index 997a2b782..e51609f4a 100644 --- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php +++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php @@ -93,6 +93,19 @@ abstract class MonitoredObjectController extends Controller $this->view->object = $this->object; } + /** + * Show the history for a host or service + */ + public function historyAction() + { + $this->getTabs()->activate('history'); + $this->view->history = $this->object->fetchEventHistory()->eventhistory; + + $this->setupLimitControl(50); + $this->setupPaginationControl($this->view->history, 50); + $this->view->object = $this->object; + } + /** * Handle a command form * @@ -145,6 +158,9 @@ abstract class MonitoredObjectController extends Controller $params = array( 'host' => $object->getName() ); + if ($this->params->has('service')) { + $params['service'] = $this->params->get('service'); + } } else { $isService = true; $params = array( @@ -165,14 +181,14 @@ abstract class MonitoredObjectController extends Controller 'urlParams' => $params ) ); - if ($isService) { + if ($isService || $this->params->has('service')) { $tabs->add( 'service', array( 'title' => sprintf( $this->translate('Show detailed information for service %s on host %s'), - $object->getName(), - $object->getHost()->getName() + $isService ? $object->getName() : $this->params->get('service'), + $isService ? $object->getHost()->getName() : $object->getName() ), 'label' => $this->translate('Service'), 'icon' => 'service', @@ -181,6 +197,19 @@ abstract class MonitoredObjectController extends Controller ) ); } + $tabs->add( + 'services', + array( + 'title' => sprintf( + $this->translate('List all services on host %s'), + $isService ? $object->getHost()->getName() : $object->getName() + ), + 'label' => $this->translate('Services'), + 'icon' => 'services', + 'url' => 'monitoring/host/services', + 'urlParams' => $params + ) + ); if ($this->backend->hasQuery('eventhistory')) { $tabs->add( 'history', @@ -195,7 +224,7 @@ abstract class MonitoredObjectController extends Controller , 'label' => $this->translate('History'), 'icon' => 'rewind', - 'url' => 'monitoring/show/history', + 'url' => $isService ? 'monitoring/service/history' : 'monitoring/host/history', 'urlParams' => $params ) ); diff --git a/modules/monitoring/module.info b/modules/monitoring/module.info index a807a5316..6e36c2838 100644 --- a/modules/monitoring/module.info +++ b/modules/monitoring/module.info @@ -1,5 +1,5 @@ Module: monitoring -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Icinga monitoring module This is the core module for most Icingaweb users. It provides an abstraction layer for various Icinga data backends. diff --git a/modules/setup/application/clicommands/ConfigCommand.php b/modules/setup/application/clicommands/ConfigCommand.php index eb5aaca83..a4a5da12e 100644 --- a/modules/setup/application/clicommands/ConfigCommand.php +++ b/modules/setup/application/clicommands/ConfigCommand.php @@ -29,7 +29,7 @@ class ConfigCommand extends Command * * icingacli setup config directory * - * icingacli setup config directory --mode 2775 --config /opt/icingaweb2/etc + * icingacli setup config directory --mode=2775 --config=/opt/icingaweb2/etc */ public function directoryAction() { @@ -96,7 +96,7 @@ class ConfigCommand extends Command * * --path= The URL path to Icinga Web 2 [/icingaweb2] * - * --root/--document-root= The directory from which the webserver will serve files [/path/to/icingaweb2/public] + * --root|--document-root= The directory from which the webserver will serve files [/path/to/icingaweb2/public] * * --config= Path to Icinga Web 2's configuration files [/etc/icingaweb2] * @@ -106,9 +106,9 @@ class ConfigCommand extends Command * * icingacli setup config webserver apache * - * icingacli setup config webserver apache --path /icingaweb2 --document-root /usr/share/icingaweb2/public --config=/etc/icingaweb2 + * icingacli setup config webserver apache --path=/icingaweb2 --document-root=/usr/share/icingaweb2/public --config=/etc/icingaweb2 * - * icingacli setup config webserver apache --file /etc/apache2/conf.d/icingaweb2.conf + * icingacli setup config webserver apache --file=/etc/apache2/conf.d/icingaweb2.conf * * icingacli setup config webserver nginx */ diff --git a/modules/setup/module.info b/modules/setup/module.info new file mode 100644 index 000000000..d874ef76c --- /dev/null +++ b/modules/setup/module.info @@ -0,0 +1,6 @@ +Module: setup +Version: 2.0.0-rc1 +Description: Setup module + Web based wizard for setting up Icinga Web 2 and its modules. + This includes the data backends (e.g. relational database, LDAP), + the authentication method, where to store the user preferences and much more. diff --git a/modules/test/application/clicommands/PhpCommand.php b/modules/test/application/clicommands/PhpCommand.php index c84000f70..59a79e0a1 100644 --- a/modules/test/application/clicommands/PhpCommand.php +++ b/modules/test/application/clicommands/PhpCommand.php @@ -41,7 +41,7 @@ class PhpCommand extends Command * * icingacli test php unit --verbose * icingacli test php unit --build - * icingacli test php unit --include *SpecialTest + * icingacli test php unit --include=*SpecialTest */ public function unitAction() { @@ -109,8 +109,8 @@ class PhpCommand extends Command * * icingacli test php style --verbose * icingacli test php style --build - * icingacli test php style --include path/to/your/file - * icingacli test php style --exclude *someFile* --exclude someOtherFile* + * icingacli test php style --include=path/to/your/file + * icingacli test php style --exclude=*someFile* --exclude=someOtherFile* */ public function styleAction() { diff --git a/modules/test/module.info b/modules/test/module.info index ed6adc1f2..b29ad31eb 100644 --- a/modules/test/module.info +++ b/modules/test/module.info @@ -1,5 +1,5 @@ Module: test -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Translation module This module allows developers to run (unit) tests against Icinga Web 2 and any of its modules. Usually you do not need to enable this. diff --git a/modules/translation/module.info b/modules/translation/module.info index ebe121ebc..098ad7981 100644 --- a/modules/translation/module.info +++ b/modules/translation/module.info @@ -1,7 +1,7 @@ Module: translation -Version: 2.0.0~alpha4 +Version: 2.0.0-rc1 Description: Translation module This module allows developers and translators to translate Icinga Web 2 and its modules for multiple languages. You do not need this module to run an internationalized web frontend. This is only for people who want to contribute - translations or translate just their own moduls. + translations or translate just their own modules. diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less index b5d5bca01..99ea074df 100644 --- a/public/css/icinga/forms.less +++ b/public/css/icinga/forms.less @@ -210,6 +210,13 @@ textarea { height: 4em; } +textarea.resource { + &.ssh-identity { + width: 50%; + height: 25em; + } +} + form .description { font-size: 0.8em; margin: 0.3em 0 0 0.6em; diff --git a/public/css/icinga/main-content.less b/public/css/icinga/main-content.less index 18bc03a57..6f15533cc 100644 --- a/public/css/icinga/main-content.less +++ b/public/css/icinga/main-content.less @@ -236,9 +236,6 @@ div.content.users { } div.controls div.user-header { - border-bottom: 2px solid @colorPetrol; - margin-bottom: 1em; - .user-name { display: inline-block; margin: 0 0 0.3em; @@ -302,9 +299,6 @@ div.content.groups { } div.controls div.group-header { - border-bottom: 2px solid @colorPetrol; - margin-bottom: 1em; - .group-name { display: inline-block; margin: 0 0 0.3em;