Allow Enabling Strict Content Security Policy (CSP) (#5059)

This commit is contained in:
Johannes Meyer 2023-08-28 16:15:09 +02:00 committed by GitHub
commit 511f507c60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 1 deletions

View File

@ -96,7 +96,18 @@ class ConfigController extends Controller
$this->assertPermission('config/general');
$form = new GeneralConfigForm();
$form->setIniConfig(Config::app());
$form->handleRequest();
$form->setOnSuccess(function (GeneralConfigForm $form) {
$config = Config::app();
$useStrictCsp = (bool) $config->get('security', 'use_strict_csp', false);
if ($form->onSuccess() === false) {
return false;
}
$appConfigForm = $form->getSubForm('form_config_general_application');
if ($appConfigForm && (bool) $appConfigForm->getValue('security_use_strict_csp') !== $useStrictCsp) {
$this->getResponse()->setReloadWindow(true);
}
})->handleRequest();
$this->view->form = $form;
$this->view->title = $this->translate('General');

View File

@ -55,6 +55,18 @@ class ApplicationConfigForm extends Form
)
);
$this->addElement(
'checkbox',
'security_use_strict_csp',
[
'label' => $this->translate('Enable strict content security policy'),
'description' => $this->translate(
'Set whether to to use strict content security policy (CSP).'
. ' This setting helps to protect from cross-site scripting (XSS).'
)
]
);
$this->addElement(
'text',
'global_module_path',

View File

@ -40,6 +40,19 @@ config_resource = "icingaweb_db"
module_path = "/usr/share/icingaweb2/modules"
```
### Security Configuration <a id="configuration-general-security"></a>
| Option | Description |
|------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
| use\_strict\_csp | **Optional.** Set this to `1` to enable strict [Content Security Policy](20-Advanced-Topics.md#advanced-topics-security-csp). Defaults to `0`. |
Example:
```
[security]
use_strict_csp = "1"
```
### Logging Configuration <a id="configuration-general-logging"></a>
Option | Description

View File

@ -4,6 +4,7 @@ This chapter provides details for advanced Icinga Web 2 topics.
* [Global URL parameters](20-Advanced-Topics.md#global-url-parameters)
* [VirtualHost configuration](20-Advanced-Topics.md#virtualhost-configuration)
* [Content Security Policy (CSP)](20-Advanced-Topics.md#advanced-topics-csp)
* [Advanced Authentication Tips](20-Advanced-Topics.md#advanced-topics-authentication-tips)
* [Source installation](20-Advanced-Topics.md#installing-from-source)
* [Automated setup](20-Advanced-Topics.md#web-setup-automation)
@ -115,6 +116,37 @@ Reload Apache and open the FQDN in your web browser.
systemctl reload httpd
```
### Content Security Policy (CSP) <a id="advanced-topics-csp"></a>
Elevate your security standards to an even higher level by enabling the [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) for Icinga Web.
Enabling strict CSP can prevent your Icinga Web environment from becoming a potential target of [Cross-Site Scripting (XSS)](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting)
and data injection attacks. After enabling this feature Icinga Web defines all the required CSP headers. Subsequently,
only content coming from Icinga Web's own origin is accepted, inline JS is prohibited, and inline CSS is accepted only
if it contains the nonce set in the response header.
We decided against enabling this by default as we cannot guarantee that all the modules out there will function correctly.
Therefore, you have to manually enable this policy explicitly and accept the risks that this might break some of
the Icinga Web modules. Icinga Web and all it's components listed below, on the other hand, fully support strict CSP. If
that's not the case, please submit an issue on GitHub in the respective repositories.
Here is a list of all Icinga Web components that are capable of strict CSP.
| Name | CSP supported since |
|-----------------------------------|-------------------------------------------------------------------------------------------|
| Icinga DB Web | [v1.1.0](https://github.com/Icinga/icingadb-web/releases/tag/v1.1.0) |
| Icinga Reporting | [v1.0.0](https://github.com/Icinga/icingaweb2-module-reporting/releases/tag/v1.0.0) |
| Icinga IDO Reports | [v0.10.1](https://github.com/Icinga/icingaweb2-module-idoreports/releases/tag/v0.10.1) |
| Icinga Cube | [v1.3.2](https://github.com/Icinga/icingaweb2-module-cube/releases/tag/v1.3.2) |
| Icinga Business Process Modeling | [v2.5.0](https://github.com/Icinga/icingaweb2-module-businessprocess/releases/tag/v2.5.0) |
| Icinga Certificate Monitoring | [v1.3.0](https://github.com/Icinga/icingaweb2-module-x509/releases/tag/v1.3.0) |
| Icinga PDF Export | [v0.10.2](https://github.com/Icinga/icingaweb2-module-pdfexport/releases/tag/v0.10.2) |
| Icinga Web Jira Integration | [v1.3.2](https://github.com/Icinga/icingaweb2-module-jira/releases/tag/v1.3.2) |
| Icinga Web Graphite Integration | [v1.3.0](https://github.com/Icinga/icingaweb2-module-graphite/releases/tag/v1.3.0) |
| Icinga Web GenericTTS Integration | [v2.1.0](https://github.com/Icinga/icingaweb2-module-generictts/releases/tag/v2.1.0) |
| Icinga Web Nagvis Integration | [v1.2.0](https://github.com/Icinga/icingaweb2-module-nagvis/releases/tag/v1.2.0) |
| Icinga Web AWS Integration | [v1.1.0](https://github.com/Icinga/icingaweb2-module-aws/releases/tag/v1.1.0) |
## Advanced Authentication Tips <a id="advanced-topics-authentication-tips"></a>
### Manual User Creation for Database Authentication Backend <a id="advanced-topics-authentication-tips-manual-user-database-auth"></a>

107
library/Icinga/Util/Csp.php Normal file
View File

@ -0,0 +1,107 @@
<?php
/* Icinga Web 2 | (c) 2023 Icinga GmbH | GPLv2+ */
namespace Icinga\Util;
use Icinga\Web\Response;
use Icinga\Web\Window;
use RuntimeException;
use function ipl\Stdlib\get_php_type;
/**
* Helper to enable strict content security policy (CSP)
*
* {@see static::addHeader()} adds a strict Content-Security-Policy header with a nonce to still support dynamic CSS
* securely.
* Note that {@see static::createNonce()} must be called first.
* Use {@see static::getStyleNonce()} to access the nonce for dynamic CSS.
*
* A nonce is not created for dynamic JS,
* and it is questionable whether this will ever be supported.
*/
class Csp
{
/** @var static */
protected static $instance;
/** @var ?string */
protected $styleNonce;
/** Singleton */
private function __construct()
{
}
/**
* Add Content-Security-Policy header with a nonce for dynamic CSS
*
* Note that {@see static::createNonce()} must be called beforehand.
*
* @param Response $response
*
* @throws RuntimeException If no nonce set for CSS
*/
public static function addHeader(Response $response): void
{
$csp = static::getInstance();
if (empty($csp->styleNonce)) {
throw new RuntimeException('No nonce set for CSS');
}
$response->setHeader('Content-Security-Policy', "style-src 'self' 'nonce-$csp->styleNonce';", true);
}
/**
* Set/recreate nonce for dynamic CSS
*
* Should always be called upon initial page loads or page reloads,
* as it sets/recreates a nonce for CSS and writes it to a window-aware session.
*/
public static function createNonce(): void
{
$csp = static::getInstance();
$csp->styleNonce = base64_encode(random_bytes(16));
Window::getInstance()->getSessionNamespace('csp')->set('style_nonce', $csp->styleNonce);
}
/**
* Get nonce for dynamic CSS
*
* @return ?string
*/
public static function getStyleNonce(): ?string
{
return static::getInstance()->styleNonce;
}
/**
* Get the CSP instance
*
* @return self
*/
protected static function getInstance(): self
{
if (static::$instance === null) {
$csp = new static();
$nonce = Window::getInstance()->getSessionNamespace('csp')->get('style_nonce');
if ($nonce !== null && ! is_string($nonce)) {
throw new RuntimeException(
sprintf(
'Nonce value is expected to be string, got %s instead',
get_php_type($nonce)
)
);
}
$csp->styleNonce = $nonce;
static::$instance = $csp;
}
return static::$instance;
}
}

View File

@ -6,6 +6,7 @@ namespace Icinga\Web\Controller;
use Icinga\Application\Modules\Module;
use Icinga\Common\PdfExport;
use Icinga\File\Pdf;
use Icinga\Util\Csp;
use Icinga\Web\View;
use ipl\I18n\Translation;
use Zend_Controller_Action;
@ -171,6 +172,10 @@ class ActionController extends Zend_Controller_Action
$this->redirectToLogin(Url::fromRequest());
}
if (! $this->isXhr() && $this->Config()->get('security', 'use_strict_csp', false)) {
Csp::createNonce();
}
$this->view->tabs = new Tabs();
$this->prepareInit();
$this->init();

View File

@ -3,6 +3,8 @@
namespace Icinga\Web;
use Icinga\Application\Config;
use Icinga\Util\Csp;
use Zend_Controller_Response_Http;
use Icinga\Application\Icinga;
use Icinga\Web\Response\JsonResponse;
@ -370,6 +372,10 @@ class Response extends Zend_Controller_Response_Http
if ($redirectUrl !== null) {
$this->setRedirect($redirectUrl->getAbsoluteUrl());
}
if (Csp::getStyleNonce() && Config::app()->get('security', 'use_strict_csp', false)) {
Csp::addHeader($this);
}
}
if (! $this->getHeader('Content-Type', true)) {