Skip to content

Allow editing of the CSP trusted image sources.#5337

Open
w1ll-i-code wants to merge 5 commits intoIcinga:mainfrom
WuerthPhoenix:5333-allow-customization-of-the-csp
Open

Allow editing of the CSP trusted image sources.#5337
w1ll-i-code wants to merge 5 commits intoIcinga:mainfrom
WuerthPhoenix:5333-allow-customization-of-the-csp

Conversation

@w1ll-i-code
Copy link

Hi, as discussed with @lippserd, we improved the CSP header and added the ability to whitelist certain trusted domains for the image sources. This closes #5333.

@cla-bot cla-bot bot added the cla/signed label Mar 20, 2025
@lippserd
Copy link
Member

Hi, as discussed with @lippserd, we improved the CSP header and added the ability to whitelist certain trusted domains for the image sources. This closes #5333.

For reference: We discussed a use case where an Icinga Web module depends on features from an external provider, such as OpenStreetMap. Without the ability to modify the CSP header, every user of the module would need to adjust or override the web server configuration. A more effective approach we considered is to implement specific functionality in Icinga Web to modify only certain parts of the header. Additionally, I think such functionality would also be necessary for https://github.com/nbuchwitz/icingaweb2-module-map for example.

@w1ll-i-code w1ll-i-code force-pushed the 5333-allow-customization-of-the-csp branch 3 times, most recently from 168628c to 43c748e Compare March 20, 2025 11:13
@lippserd lippserd requested a review from nilmerg May 13, 2025 14:30
Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some style issues as well. Please rebase with main and the actions will run and point this out.

@w1ll-i-code w1ll-i-code force-pushed the 5333-allow-customization-of-the-csp branch 3 times, most recently from 4ffe492 to d31bc33 Compare May 13, 2025 16:12
@nilmerg
Copy link
Member

nilmerg commented May 14, 2025

I just noticed this breaks our own font loading, so all our icons are invisible since font-src: 'self' prohibits them. You can notice this in the menu pretty well for example.

In addition, I've already talked about this with you in person, I'd rather like to make this an automatic whitelisting. With the recent security release, we sandboxed iframes and automatically open them up in case a user configures a navigation item or dashboard with an external link. I'm thinking about the same with img-src and frame-src. So there could be a hook or something similar that would allow your module to announce that it requires images from a specific non-origin host and that gets automatically whitelisted using the desired policy.

So, I'm afraid, we need to re-think that. I also fear complications with child-src and connect-src. I'll promise we figure that out before the next major, so 2.13 will include something to resolve this.

@w1ll-i-code w1ll-i-code force-pushed the 5333-allow-customization-of-the-csp branch from d31bc33 to bbf35b5 Compare May 15, 2025 10:10
@w1ll-i-code w1ll-i-code force-pushed the 5333-allow-customization-of-the-csp branch from bbf35b5 to 2a9adc0 Compare May 15, 2025 10:12
@w1ll-i-code w1ll-i-code requested a review from nilmerg May 15, 2025 11:51
Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for keeping at it!

I didn't look at it entirely yet. Only one thing for now:

The way you load navigation items is not correct yet. You forgot dashlets and you only load shared items the user is an owner of.

Take a look at \Icinga\Web\Navigation\Navigation::load for example. Though, please note that for dashlets a more complex solution is needed. Dashlets by modules can be loaded there (and should! Also modules may provide external URLs this way), but not user dashlets. This is done by \Icinga\Web\Widget\Dashboard::load instead.

Also, please remember that there are additional types of navigation items. A module can provide its own as well using \Icinga\Application\Modules\Module::provideNavigationItem. Monitoring's and Icinga DB Web's host and service actions are an example which may also result in an iframe.

So please restore the NavigationController and think about an alternative to the NavigationItemHelper.

@cla-bot
Copy link

cla-bot bot commented Aug 6, 2025

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Davide Zeni.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@cla-bot cla-bot bot removed the cla/signed label Aug 6, 2025
@cla-bot
Copy link

cla-bot bot commented Aug 6, 2025

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Davide Zeni.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@zenosaaur zenosaaur force-pushed the 5333-allow-customization-of-the-csp branch from 3e68e02 to 47c9736 Compare August 6, 2025 15:48
@cla-bot
Copy link

cla-bot bot commented Aug 6, 2025

Thank you for your pull request and welcome to our community. We could not parse the GitHub identity of the following contributors: Davide Zeni.
This is most likely caused by a git client misconfiguration; please make sure to:

  1. check if your git client is configured with an email to sign commits git config --list | grep email
  2. If not, set it up using git config --global user.email email@example.com
  3. Make sure that the git commit email is configured in your GitHub account settings, see https://github.com/settings/emails

@zenosaaur zenosaaur force-pushed the 5333-allow-customization-of-the-csp branch from 47c9736 to 78bfc90 Compare August 6, 2025 16:06
@cla-bot
Copy link

cla-bot bot commented Aug 6, 2025

Thank you for your pull request. Before we can look at it, you'll need to sign a Contributor License Agreement (CLA).

Please follow instructions at https://icinga.com/company/contributor-agreement to sign the CLA.

After that, please reply here with a comment and we'll verify.

Contributors that have not signed yet: @zenosaaur

Details
  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Please contact us if you think this is the case.

  • If you signed the CLA as a corporation, your GitHub username may not have been submitted to us. Please reach out to the responsible person in your organization.

@gianlucapiccolo
Copy link
Contributor

hello @nilmerg can you please give us a feedback on the changes we made? Thanks!!

@cla-bot
Copy link

cla-bot bot commented Aug 25, 2025

Thank you for your pull request. Before we can look at it, you'll need to sign a Contributor License Agreement (CLA).

Please follow instructions at https://icinga.com/company/contributor-agreement to sign the CLA.

After that, please reply here with a comment and we'll verify.

Contributors that have not signed yet: @zenosaaur

Details
  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Please contact us if you think this is the case.

  • If you signed the CLA as a corporation, your GitHub username may not have been submitted to us. Please reach out to the responsible person in your organization.

@cla-bot
Copy link

cla-bot bot commented Sep 2, 2025

Thank you for your pull request. Before we can look at it, you'll need to sign a Contributor License Agreement (CLA).

Please follow instructions at https://icinga.com/company/contributor-agreement to sign the CLA.

After that, please reply here with a comment and we'll verify.

Contributors that have not signed yet: @zenosaaur

Details
  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Please contact us if you think this is the case.

  • If you signed the CLA as a corporation, your GitHub username may not have been submitted to us. Please reach out to the responsible person in your organization.


namespace Icinga\Application\Hook;

abstract class CspDirectiveHook
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a new hook, add the new methods just like in #5433:

  • all()
  • register()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in b939340 (#5477)

Copy link
Member

@nilmerg nilmerg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase with main as the CI configuration changed.

Didn't test yet but it looks promising already!

v2.13 is on the horizon, so ensure to get back to it soon if you want this being part of it. Or drop a comment to let me delegate this further if you don't have the time. (But then allow collaborator access for us)

$this->view->title = $navigation->getLabel();
}
}
} No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please restore the empty line at the end of the file as well.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes to NavigationControllerhave been reverted in #5477

@@ -0,0 +1,18 @@
<?php
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-3.0-or-later

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither I'd add GPLv3 files in a (yet) GPLv2 project (our corporate lawyer has left the chat 🙈), nor I'd mix license header formats in one and the same project. Latter especially because the GPLv3 PR (which doesn't even exist yet, by the way!) will likely use xargs to apply the same command to all files. The more files have the same header format, the better.

Comment on lines +229 to +252
$user = Auth::getInstance()->getUser();
$menuItems = [];
if ($user === null) {
return $menuItems;
}
$navigationType = Navigation::getItemTypeConfiguration();
foreach ($navigationType as $type => $_) {
$config = Config::navigation($type, $user->getUsername());
$config->getConfigObject()->setKeyColumn('name');
foreach ($config->select() as $itemConfig) {
if ($itemConfig->get("target", "") !== "_blank") {
$menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')];
}
}
$configShared = Config::navigation($type);
$configShared->getConfigObject()->setKeyColumn('name');
foreach ($configShared->select() as $itemConfig) {
if (Icinga::app()->hasAccessToSharedNavigationItem($itemConfig, $config) && $itemConfig->get("target", "") !== "_blank") {
$menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')];
}
}
}
return $menuItems;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was rather thinking about a solution like this: (Untested)

I didn't ensure this fits with your remaining code, so adjust it accordingly as you see fit.

Suggested change
$user = Auth::getInstance()->getUser();
$menuItems = [];
if ($user === null) {
return $menuItems;
}
$navigationType = Navigation::getItemTypeConfiguration();
foreach ($navigationType as $type => $_) {
$config = Config::navigation($type, $user->getUsername());
$config->getConfigObject()->setKeyColumn('name');
foreach ($config->select() as $itemConfig) {
if ($itemConfig->get("target", "") !== "_blank") {
$menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')];
}
}
$configShared = Config::navigation($type);
$configShared->getConfigObject()->setKeyColumn('name');
foreach ($configShared->select() as $itemConfig) {
if (Icinga::app()->hasAccessToSharedNavigationItem($itemConfig, $config) && $itemConfig->get("target", "") !== "_blank") {
$menuItems[] = ["name" => $itemConfig->get('name'), "url" => $itemConfig->get('url')];
}
}
}
return $menuItems;
}
if (! Auth::isAuthenticated()) {
return [];
}
$origins = [];
foreach (Navigation::load($type) as $navItem) {
foreach (self::yieldNavigation($navItem) as $name => $url) {
$origins[] = $url->getScheme() . '://' . $url->getHost();
}
}
return $origins;
}
protected static function yieldNavigation(NavigationItem $item): Generator
{
if ($item->hasChildren()) {
foreach ($item as $child) {
yield from self::yieldNavigation($child);
}
} else {
if ($item->getTarget() !== '_blank' && $item->getUrl()->isExternal()) {
yield $item->getName() => $item->getUrl();
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented your suggestion in #5477

}

private function hasAccessToSharedNavigationItem(&$config, Config $navConfig)
public function hasAccessToSharedNavigationItem(&$config, Config $navConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's needed if you use my proposal.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Reverted it in #5477

Comment on lines +91 to +105
$errorSource = sprintf("Navigation item %s", $navigationItem['name']);

$host = parse_url($navigationItem["url"], PHP_URL_HOST);
// Make sure $url is actually valid;
if (filter_var($navigationItem["url"], FILTER_VALIDATE_URL) === false) {
Logger::debug("$errorSource: Skipping invalid url: $host");
continue;
}

$scheme = parse_url($navigationItem["url"], PHP_URL_SCHEME);


if ($host === null) {
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validation is already performed at least partially in the other methods. Please make sure to do this only once. Preferably during the fetch logic, as in my proposal.

@lippserd
Copy link
Member

Please excuse me for not informing everyone, but I discussed with WP that we will take over this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow customization of the CSP

7 participants