Merge branch 'feature/ssh-remote-connection-resource-configuration-7595'

resolves #7595
This commit is contained in:
Matthias Jentsch 2015-06-15 17:30:42 +02:00
commit 42de13a2b9
9 changed files with 380 additions and 17 deletions

View File

@ -0,0 +1,147 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Config\Resource;
use Icinga\Application\Icinga;
use Icinga\Data\ConfigObject;
use Icinga\Forms\Config\ResourceConfigForm;
use Icinga\Web\Form;
use Icinga\Util\File;
use Zend_Validate_Callback;
/**
* Form class for adding/modifying ssh identity resources
*/
class SshResourceForm extends Form
{
/**
* Initialize this form
*/
public function init()
{
$this->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(
'<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
$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;
}
}

View File

@ -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';

View File

@ -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.
### <a id="resources-configuration-database"></a> Database
@ -64,6 +64,26 @@ bind_dn = "cn=admin,ou=people,dc=icinga,dc=org"
bind_pw = admin`
````
### <a id="resources-configuration-ssh"></a> 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"
````
### <a id="resources-configuration-livestatus"></a> Livestatus
A Livestatus resource represents the location of a Livestatus socket which is used for fetching monitoring data.

View File

@ -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();

View File

@ -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(
'<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
$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;
}
}

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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),

View File

@ -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;