icingaweb2-module-director/application/controllers/SelfServiceController.php

427 lines
14 KiB
PHP

<?php
namespace Icinga\Module\Director\Controllers;
use Exception;
use Icinga\Exception\NotFoundError;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Forms\IcingaHostSelfServiceForm;
use Icinga\Module\Director\Objects\IcingaEndpoint;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaZone;
use Icinga\Module\Director\Settings;
use Icinga\Module\Director\Util;
use Icinga\Module\Director\Web\Controller\ActionController;
use ipl\Html\Html;
class SelfServiceController extends ActionController
{
/** @var bool */
protected $isApified = true;
/** @var bool */
protected $requiresAuthentication = false;
/** @var Settings */
protected $settings;
protected function assertApiPermission()
{
// no permission required, we'll check the API key
}
protected function checkDirectorPermissions()
{
}
/**
* @throws NotFoundError
* @throws ProgrammingError
* @throws \Zend_Controller_Request_Exception
*/
public function apiVersionAction()
{
if ($this->getRequest()->isApiRequest()) {
$this->sendPowerShellResponse('1.4.0');
} else {
throw new NotFoundError('Not found');
}
}
/**
* @throws \Icinga\Exception\ConfigurationError
* @throws \Icinga\Exception\IcingaException
* @throws \Zend_Controller_Exception
*/
public function registerHostAction()
{
$request = $this->getRequest();
$form = IcingaHostSelfServiceForm::create($this->db());
$form->setApiRequest($request->isApiRequest());
try {
if ($key = $this->params->get('key')) {
$form->loadTemplateWithApiKey($key);
}
} catch (Exception $e) {
$this->sendPowerShellError($e->getMessage(), 404);
return;
}
if ($name = $this->params->get('name')) {
$form->setHostName($name);
}
if ($request->isApiRequest()) {
$data = json_decode($request->getRawBody());
$request->setPost((array) $data);
$form->handleRequest();
if ($newKey = $form->getHostApiKey()) {
$this->sendPowerShellResponse($newKey);
} else {
$error = implode('; ', $form->getErrorMessages());
if ($error === '') {
foreach ($form->getErrors() as $elName => $errors) {
if (in_array('isEmpty', $errors)) {
$this->sendPowerShellError(
sprintf("%s is required", $elName),
400
);
return;
} else {
$this->sendPowerShellError('An unknown error ocurred', 500);
}
}
} else {
$this->sendPowerShellError($error, 400);
}
}
return;
}
$form->handleRequest();
$this->addSingleTab($this->translate('Self Service'))
->addTitle($this->translate('Self Service - Host Registration'))
->content()->add(Html::tag('p', null, $this->translate(
'In case an Icinga Admin provided you with a self service API'
. ' token, this is where you can register new hosts'
)))
->add($form);
}
/**
* @throws NotFoundError
* @throws \Zend_Controller_Request_Exception
* @throws \Zend_Controller_Response_Exception
*/
public function ticketAction()
{
if (!$this->getRequest()->isApiRequest()) {
throw new NotFoundError('Not found');
}
try {
$key = $this->params->getRequired('key');
$host = IcingaHost::loadWithApiKey($key, $this->db());
if ($host->isTemplate()) {
throw new NotFoundError('Got invalid API key "%s"', $key);
}
$name = $host->getObjectName();
if ($host->getResolvedProperty('has_agent') !== 'y') {
throw new NotFoundError('The host "%s" is not an agent', $name);
}
$this->sendPowerShellResponse(
Util::getIcingaTicket(
$name,
$this->api()->getTicketSalt()
)
);
} catch (Exception $e) {
if ($e instanceof NotFoundError) {
$this->sendPowerShellError($e->getMessage(), 404);
} else {
$this->sendPowerShellError($e->getMessage(), 500);
}
}
}
/**
* @param $response
* @throws ProgrammingError
* @throws \Zend_Controller_Request_Exception
*/
protected function sendPowerShellResponse($response)
{
if ($this->getRequest()->getHeader('X-Director-Accept') === 'text/plain') {
if (is_array($response)) {
echo $this->makePlainTextPowerShellArray($response);
} else {
echo $response;
}
} else {
$this->sendJson($this->getResponse(), $response);
}
}
/**
* @param $error
* @param $code
* @throws \Zend_Controller_Request_Exception
* @throws \Zend_Controller_Response_Exception
*/
protected function sendPowerShellError($error, $code)
{
$this->getResponse()->setHttpResponseCode($code);
if ($this->getRequest()->getHeader('X-Director-Accept') === 'text/plain') {
echo "ERROR: $error";
} else {
$this->sendJsonError($this->getResponse(), $error);
}
exit;
}
/**
* @param $value
* @return string
* @throws ProgrammingError
*/
protected function makePowerShellBoolean($value)
{
if ($value === 'y' || $value === true) {
return 'true';
} elseif ($value === 'n' || $value === false) {
return 'false';
} else {
throw new ProgrammingError(
'Expected boolean value, got %s',
var_export($value, 1)
);
}
}
/**
* @param array $params
* @return string
* @throws ProgrammingError
*/
protected function makePlainTextPowerShellArray(array $params)
{
$plain = '';
foreach ($params as $key => $value) {
if (is_bool($value)) {
$value = $this->makePowerShellBoolean($value);
} elseif (is_array($value)) {
$value = implode('!', $value);
}
$plain .= "$key: $value\r\n";
}
return $plain;
}
/**
* @throws NotFoundError
* @throws \Zend_Controller_Request_Exception
* @throws \Zend_Controller_Response_Exception
*/
public function powershellParametersAction()
{
if (!$this->getRequest()->isApiRequest()) {
throw new NotFoundError('Not found');
}
try {
$this->shipPowershellParams();
} catch (Exception $e) {
if ($e instanceof NotFoundError) {
$this->sendPowerShellError($e->getMessage(), 404);
} else {
$this->sendPowerShellError($e->getMessage(), 500);
}
}
}
/**
* @throws NotFoundError
* @throws ProgrammingError
* @throws \Icinga\Exception\ConfigurationError
* @throws \Icinga\Exception\IcingaException
* @throws \Icinga\Exception\MissingParameterException
* @throws \Zend_Controller_Request_Exception
* @throws \Zend_Controller_Response_Exception
*/
protected function shipPowershellParams()
{
$db = $this->db();
$key = $this->params->getRequired('key');
$host = IcingaHost::loadWithApiKey($key, $db);
$settings = $this->getSettings();
$transform = $settings->get('self-service/transform_hostname');
$params = [
'fetch_agent_name' => $settings->get('self-service/agent_name') === 'hostname',
'fetch_agent_fqdn' => $settings->get('self-service/agent_name') === 'fqdn',
'transform_hostname' => $transform,
'flush_api_directory' => $settings->get('self-service/flush_api_dir') === 'y'
];
$username = $settings->get('self-service/icinga_service_user');
if (strlen($username)) {
$params['icinga_service_user'] = $username;
}
if ($transform === '2') {
$transformMethod = '.upperCase';
} elseif ($transform === '1') {
$transformMethod = '.lowerCase';
} else {
$transformMethod = '';
}
$hostObject = (object) [
'address' => '&ipaddress&',
];
switch ($settings->get('self-service/agent_name')) {
case 'hostname':
$hostObject->display_name = "&fqdn$transformMethod&";
break;
case 'fqdn':
$hostObject->display_name = "&hostname$transformMethod&";
break;
}
$params['director_host_object'] = json_encode($hostObject);
if ($settings->get('self-service/download_type')) {
$params['download_url'] = $settings->get('self-service/download_url');
$params['agent_version'] = $settings->get('self-service/agent_version');
$params['allow_updates'] = $settings->get('self-service/allow_updates') === 'y';
$params['agent_listen_port'] = $host->getAgentListenPort();
if ($hashes = $settings->get('self-service/installer_hashes')) {
$params['installer_hashes'] = $hashes;
}
if ($settings->get('self-service/install_nsclient') === 'y') {
$params['install_nsclient'] = true;
$this->addBooleanSettingsToParams($settings, [
'nsclient_add_defaults',
'nsclient_firewall',
'nsclient_service',
], $params);
$this->addStringSettingsToParams($settings, [
'nsclient_directory',
'nsclient_installer_path'
], $params);
}
}
$this->addHostToParams($host, $params);
if ($this->getRequest()->getHeader('X-Director-Accept') === 'text/plain') {
echo $this->makePlainTextPowerShellArray($params);
} else {
$this->sendJson($this->getResponse(), $params);
}
}
/**
* @param IcingaHost $host
* @param array $params
* @throws NotFoundError
* @throws ProgrammingError
* @throws \Icinga\Exception\ConfigurationError
* @throws \Icinga\Exception\IcingaException
* @throws \Zend_Controller_Request_Exception
* @throws \Zend_Controller_Response_Exception
*/
protected function addHostToParams(IcingaHost $host, array &$params)
{
if (! $host->isObject()) {
return;
}
$db = $this->db();
$settings = $this->getSettings();
$name = $host->getObjectName();
if ($host->getSingleResolvedProperty('has_agent') !== 'y') {
$this->sendPowerShellError(sprintf(
'%s is not configured for Icinga Agent usage',
$name
), 403);
return;
}
$zoneName = $host->getRenderingZone();
if ($zoneName === IcingaHost::RESOLVE_ERROR) {
$this->sendPowerShellError(sprintf(
'Could not resolve target Zone for %s',
$name
), 404);
return;
}
$masterConnectsToAgent = $host->getSingleResolvedProperty(
'master_should_connect'
) === 'y';
$params['agent_add_firewall_rule'] = $masterConnectsToAgent;
$params['global_zones'] = $settings->get('self-service/global_zones');
$zone = IcingaZone::load($zoneName, $db);
$endpointNames = $zone->listEndpoints();
if (! $masterConnectsToAgent) {
$endpointsConfig = [];
foreach ($endpointNames as $endpointName) {
$endpoint = IcingaEndpoint::load($endpointName, $db);
$endpointsConfig[] = sprintf(
'%s;%s',
$endpoint->getSingleResolvedProperty('host'),
$endpoint->getResolvedPort()
);
}
$params['endpoints_config'] = $endpointsConfig;
}
$master = $db->getDeploymentEndpoint();
$params['parent_zone'] = $zoneName;
$params['ca_server'] = $master->getObjectName();
$params['parent_endpoints'] = $endpointNames;
$params['accept_config'] = $host->getSingleResolvedProperty('accept_config')=== 'y';
}
protected function addStringSettingsToParams(Settings $settings, array $keys, array &$params)
{
foreach ($keys as $key) {
$value = $settings->get("self-service/$key");
if (strlen($value)) {
$params[$key] = $value;
}
}
}
protected function addBooleanSettingsToParams(Settings $settings, array $keys, array &$params)
{
foreach ($keys as $key) {
$value = $settings->get("self-service/$key");
if ($value !== null) {
$params[$key] = $value === 'y';
}
}
}
/**
* @return Settings
* @throws \Icinga\Exception\ConfigurationError
*/
protected function getSettings()
{
if ($this->settings === null) {
$this->settings = new Settings($this->db());
}
return $this->settings;
}
}